diff options
author | RĂ©mi Verschelde <rverschelde@gmail.com> | 2020-10-15 10:28:59 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-10-15 10:28:59 +0200 |
commit | cb3d5b6ddaf231664b480997b9e640cc0b695c45 (patch) | |
tree | e2f85338c45019cdc924c6ed7f9a355c1d02c7c1 | |
parent | 075a8baa363e1048b31647298d7116b720511096 (diff) | |
parent | c54de7f5899f2bd64caee65efe16975a179aa51f (diff) |
Merge pull request #42789 from Faless/js/4.0_html_editor_first_iteration
[HTML5] Editor prototype
-rw-r--r-- | drivers/dummy/rasterizer_dummy.h | 24 | ||||
-rw-r--r-- | misc/dist/html/editor.html | 475 | ||||
-rw-r--r-- | platform/javascript/SCsub | 3 | ||||
-rw-r--r-- | platform/javascript/api/api.cpp | 2 | ||||
-rw-r--r-- | platform/javascript/api/javascript_tools_editor_plugin.cpp | 153 | ||||
-rw-r--r-- | platform/javascript/api/javascript_tools_editor_plugin.h | 62 | ||||
-rw-r--r-- | platform/javascript/detect.py | 2 | ||||
-rw-r--r-- | platform/javascript/engine/engine.js | 8 | ||||
-rw-r--r-- | platform/javascript/native/utils.js | 21 |
9 files changed, 745 insertions, 5 deletions
diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index bdb51b1573..1df26756c2 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -154,6 +154,7 @@ public: void render_material(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID p_framebuffer, const Rect2i &p_region) override {} void render_sdfgi(RID p_render_buffers, int p_region, InstanceBase **p_cull_result, int p_cull_count) override {} void render_sdfgi_static_lights(RID p_render_buffers, uint32_t p_cascade_count, const uint32_t *p_cascade_indices, const RID **p_positional_light_cull_result, const uint32_t *p_positional_light_cull_count) override {} + void render_particle_collider_heightfield(RID p_collider, const Transform &p_transform, InstanceBase **p_cull_result, int p_cull_count) override {} void set_scene_pass(uint64_t p_pass) override {} void set_time(double p_time, double p_step) override {} @@ -802,6 +803,7 @@ public: void particles_set_fractional_delta(RID p_particles, bool p_enable) override {} void particles_set_subemitter(RID p_particles, RID p_subemitter_particles) override {} void particles_set_view_axis(RID p_particles, const Vector3 &p_axis) override {} + void particles_set_collision_base_size(RID p_particles, float p_size) override {} void particles_restart(RID p_particles) override {} void particles_set_draw_order(RID p_particles, RS::ParticlesDrawOrder p_order) override {} @@ -819,6 +821,28 @@ public: int particles_get_draw_passes(RID p_particles) const override { return 0; } RID particles_get_draw_pass_mesh(RID p_particles, int p_pass) const override { return RID(); } + void particles_add_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance) override {} + void particles_remove_collision(RID p_particles, RasterizerScene::InstanceBase *p_instance) override {} + + void update_particles() override {} + + /* PARTICLES COLLISION */ + + RID particles_collision_create() override { return RID(); } + void particles_collision_set_collision_type(RID p_particles_collision, RS::ParticlesCollisionType p_type) override {} + void particles_collision_set_cull_mask(RID p_particles_collision, uint32_t p_cull_mask) override {} + void particles_collision_set_sphere_radius(RID p_particles_collision, float p_radius) override {} + void particles_collision_set_box_extents(RID p_particles_collision, const Vector3 &p_extents) override {} + void particles_collision_set_attractor_strength(RID p_particles_collision, float p_strength) override {} + void particles_collision_set_attractor_directionality(RID p_particles_collision, float p_directionality) override {} + void particles_collision_set_attractor_attenuation(RID p_particles_collision, float p_curve) override {} + void particles_collision_set_field_texture(RID p_particles_collision, RID p_texture) override {} + void particles_collision_height_field_update(RID p_particles_collision) override {} + void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) override {} + AABB particles_collision_get_aabb(RID p_particles_collision) const override { return AABB(); } + bool particles_collision_is_heightfield(RID p_particles_collision) const override { return false; } + RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const override { return RID(); } + /* GLOBAL VARIABLES */ void global_variable_add(const StringName &p_name, RS::GlobalVariableType p_type, const Variant &p_value) override {} diff --git a/misc/dist/html/editor.html b/misc/dist/html/editor.html new file mode 100644 index 0000000000..5b6ad2df65 --- /dev/null +++ b/misc/dist/html/editor.html @@ -0,0 +1,475 @@ +<!DOCTYPE html> +<html xmlns='http://www.w3.org/1999/xhtml' lang='' xml:lang=''> +<head> + <meta charset='utf-8' /> + <meta name='viewport' content='width=device-width, user-scalable=no' /> + <link id='-gd-engine-icon' rel='icon' type='image/png' href='favicon.png' /> + <title></title> + <style type='text/css'> + + body { + touch-action: none; + margin: 0; + border: 0 none; + padding: 0; + text-align: center; + background-color: black; + overflow: hidden; + } + + #canvas, #gameCanvas { + display: block; + margin: 0; + color: white; + } + + #canvas:focus, #gameCanvas:focus { + outline: none; + } + + .godot { + font-family: 'Noto Sans', 'Droid Sans', Arial, sans-serif; + color: #e0e0e0; + background-color: #3b3943; + background-image: linear-gradient(to bottom, #403e48, #35333c); + border: 1px solid #45434e; + box-shadow: 0 0 1px 1px #2f2d35; + } + + + /* Status display + * ============== */ + + #status { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + /* don't consume click events - make children visible explicitly */ + visibility: hidden; + } + + #status-progress { + width: 366px; + height: 7px; + background-color: #38363A; + border: 1px solid #444246; + padding: 1px; + box-shadow: 0 0 2px 1px #1B1C22; + border-radius: 2px; + visibility: visible; + } + + @media only screen and (orientation:portrait) { + #status-progress { + width: 61.8%; + } + } + + #status-progress-inner { + height: 100%; + width: 0; + box-sizing: border-box; + transition: width 0.5s linear; + background-color: #202020; + border: 1px solid #222223; + box-shadow: 0 0 1px 1px #27282E; + border-radius: 3px; + } + + #status-indeterminate { + visibility: visible; + position: relative; + } + + #status-indeterminate > div { + width: 4.5px; + height: 0; + border-style: solid; + border-width: 9px 3px 0 3px; + border-color: #2b2b2b transparent transparent transparent; + transform-origin: center 21px; + position: absolute; + } + + #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } + #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } + #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } + #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } + #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } + #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } + #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } + #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } + + #status-notice { + margin: 0 100px; + line-height: 1.3; + visibility: visible; + padding: 4px 6px; + visibility: visible; + } + </style> +</head> +<body> + <div id="tabs-buttons"> + <button id="btn-tab-loader" class="tab-btn" onclick="showTab('loader')">Loader</button> + <button id="btn-tab-editor" class="tab-btn" disabled="disabled" onclick="showTab('editor')">Editor</button> + <button id="btn-close-editor" class="close-btn" disabled="disabled" onclick="closeEditor()">X</button> + <button id="btn-tab-game" class="tab-btn" disabled="disabled" onclick="showTab('game')">Game</button> + <button id="btn-close-game" class="close-btn" disabled="disabled" onclick="closeGame()">X</button> + </div> + <div id='tabs'> + <div id='tab-loader'> + <div style="color: white;" id="persistence"> + <label for="videoMode" style="display: none;">Select video driver:</label><br /> + <select id="videoMode" style="display: none;"> + <option value="GLES2" selected="selected">WebGL</option> + <option value="GLES3">WebGL 2</option> + </select> + <br /> + <label for="zip-file">Preload project zip: </label><input id="zip-file" type="file" id="files" name="files"/> + <br /> + <button id="startButton">Start Godot Editor</button> + <br /> + <br /> + <button onclick="clearPersistence()">Clear persistent data</button> + </div> + </div> + <div id='tab-editor' style="display: none;"> + <canvas id='editor-canvas' tabindex="1"> + HTML5 canvas appears to be unsupported in the current browser.<br /> + Please try updating or use a different browser. + </canvas> + </div> + <div id='tab-game' style="display: none;"> + <canvas id='game-canvas' tabindex="2"> + HTML5 canvas appears to be unsupported in the current browser.<br /> + Please try updating or use a different browser. + </canvas> + </div> + <div id='tab-status' style="display: none;"> + <div id='status-progress' style='display: none;' oncontextmenu='event.preventDefault();'><div id ='status-progress-inner'></div></div> + <div id='status-indeterminate' style='display: none;' oncontextmenu='event.preventDefault();'> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <div id='status-notice' class='godot' style='display: none;'></div> + </div> + </div> + + <script type='text/javascript' src='godot.tools.js'></script> + <script type='text/javascript'>//<![CDATA[ + + var engine = new Engine; + var game = null; + var setStatusMode; + var setStatusNotice; + var video_driver = "GLES2"; + + function clearPersistence() { + function deleteDB(path) { + return new Promise(function(resolve, reject) { + var req = indexedDB.deleteDatabase(path); + req.onsuccess = function() { + resolve(); + }; + req.onerror = function(err) { + reject(err); + }; + req.onblocked = function(err) { + reject(err); + } + + }); + } + if (!window.confirm("Are you sure you want to delete all the locally stored files?")) { + return; + } + Promise.all([ + deleteDB("/home/web_user/projects"), + deleteDB("/home/web_user/.config") + ]).then(function(results) { + alert("Done."); + }).catch(function (err) { + alert("Error deleting local files. Please retry after reloading the page."); + }); + } + + function selectVideoMode() { + var select = document.getElementById('videoMode'); + video_driver = select.selectedOptions[0].value; + } + + var tabs = [ + document.getElementById('tab-loader'), + document.getElementById('tab-editor'), + document.getElementById('tab-game') + ] + function showTab(name) { + tabs.forEach(function (elem) { + if (elem.id == 'tab-' + name) { + elem.style.display = 'block'; + } else { + elem.style.display = 'none'; + } + }); + } + + function setButtonEnabled(id, enabled) { + if (enabled) { + document.getElementById(id).disabled = ""; + } else { + document.getElementById(id).disabled = "disabled"; + } + } + + function setLoaderEnabled(enabled) { + setButtonEnabled('btn-tab-loader', enabled); + setButtonEnabled('btn-tab-editor', !enabled); + setButtonEnabled('btn-close-editor', !enabled); + } + + function setGameTabEnabled(enabled) { + setButtonEnabled('btn-tab-game', enabled); + setButtonEnabled('btn-close-game', enabled); + } + + function closeGame() { + if (game) { + game.requestQuit(); + } + } + + function closeEditor() { + closeGame(); + if (engine) { + engine.requestQuit(); + } + } + + function startEditor(zip) { + + const INDETERMINATE_STATUS_STEP_MS = 100; + const persistentPaths = ['/home/web_user/.config', '/home/web_user/projects']; + + var editorCanvas = document.getElementById('editor-canvas'); + var gameCanvas = document.getElementById('game-canvas'); + var statusProgress = document.getElementById('status-progress'); + var statusProgressInner = document.getElementById('status-progress-inner'); + var statusIndeterminate = document.getElementById('status-indeterminate'); + var statusNotice = document.getElementById('status-notice'); + + var initializing = true; + var statusMode = 'hidden'; + + showTab('status'); + + var animationCallbacks = []; + function animate(time) { + animationCallbacks.forEach(callback => callback(time)); + requestAnimationFrame(animate); + } + requestAnimationFrame(animate); + + function adjustCanvasDimensions() { + var scale = window.devicePixelRatio || 1; + var header = document.getElementById('tabs-buttons'); + var headerHeight = header.offsetHeight + 1; + var width = window.innerWidth; + var height = window.innerHeight - headerHeight; + editorCanvas.width = width * scale; + editorCanvas.height = height * scale; + editorCanvas.style.width = width + "px"; + editorCanvas.style.height = height + "px"; + } + animationCallbacks.push(adjustCanvasDimensions); + adjustCanvasDimensions(); + + setStatusMode = function setStatusMode(mode) { + + if (statusMode === mode || !initializing) + return; + [statusProgress, statusIndeterminate, statusNotice].forEach(elem => { + elem.style.display = 'none'; + }); + animationCallbacks = animationCallbacks.filter(function(value) { + return (value != animateStatusIndeterminate); + }); + switch (mode) { + case 'progress': + statusProgress.style.display = 'block'; + break; + case 'indeterminate': + statusIndeterminate.style.display = 'block'; + animationCallbacks.push(animateStatusIndeterminate); + break; + case 'notice': + statusNotice.style.display = 'block'; + break; + case 'hidden': + break; + default: + throw new Error('Invalid status mode'); + } + statusMode = mode; + } + + function animateStatusIndeterminate(ms) { + + var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8); + if (statusIndeterminate.children[i].style.borderTopColor == '') { + Array.prototype.slice.call(statusIndeterminate.children).forEach(child => { + child.style.borderTopColor = ''; + }); + statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; + } + } + + setStatusNotice = function setStatusNotice(text) { + + while (statusNotice.lastChild) { + statusNotice.removeChild(statusNotice.lastChild); + } + var lines = text.split('\n'); + lines.forEach((line) => { + statusNotice.appendChild(document.createTextNode(line)); + statusNotice.appendChild(document.createElement('br')); + }); + }; + + engine.setProgressFunc((current, total) => { + + if (total > 0) { + statusProgressInner.style.width = current/total * 100 + '%'; + setStatusMode('progress'); + if (current === total) { + // wait for progress bar animation + setTimeout(() => { + setStatusMode('indeterminate'); + }, 100); + } + } else { + setStatusMode('indeterminate'); + } + }); + + engine.setPersistentPaths(persistentPaths); + + engine.setOnExecute(function(args) { + const is_editor = args.filter(function(v) { return v == '--editor' || v == '-e' }).length != 0; + const is_project_manager = args.filter(function(v) { return v == '--project-manager' }).length != 0; + const is_game = !is_editor && !is_project_manager; + if (is_project_manager) { + args.push('--video-driver', video_driver); + } + if (is_game) { + if (game) { + console.error("A game is already running. Close it first"); + return; + } + setGameTabEnabled(true); + game = new Engine(); + game.setPersistentPaths(persistentPaths); + game.setUnloadAfterInit(false); + game.setOnExecute(engine.onExecute); + game.setCanvas(gameCanvas); + game.setCanvasResizedOnStart(true); + game.setOnExit(function() { + setGameTabEnabled(false); + showTab('editor'); + game = null; + }); + showTab('game'); + game.init().then(function() { + requestAnimationFrame(function() { + game.start.apply(game, args).then(function() { + gameCanvas.focus(); + }); + }); + }); + } else { // New editor instances will be run in the same canvas. We want to wait for it to exit. + engine.setOnExit(function(code) { + setLoaderEnabled(true); + setTimeout(function() { + engine.init().then(function() { + setLoaderEnabled(false); + engine.setOnExit(function() { + showTab('loader'); + setLoaderEnabled(true); + }); + engine.start.apply(engine, args); + }); + }, 0); + engine.setOnExit(null); + }); + } + }); + + function displayFailureNotice(err) { + var msg = err.message || err; + console.error(msg); + setStatusNotice(msg); + setStatusMode('notice'); + initializing = false; + }; + + if (!Engine.isWebGLAvailable()) { + displayFailureNotice('WebGL not available'); + } else { + setStatusMode('indeterminate'); + engine.setCanvas(editorCanvas); + engine.setUnloadAfterInit(false); // Don't want to reload when starting game. + engine.init('godot.tools').then(function() { + if (zip) { + engine.copyToFS("/home/web_user/preload.zip", zip); + } + try { + // Avoid user creating project in the persistent root folder. + engine.copyToFS("/home/web_user/projects/keep", new Uint8Array()); + } catch(e) { + // File exists + } + //selectVideoMode(); + showTab('editor'); + setLoaderEnabled(false); + engine.setOnExit(function() { + showTab('loader'); + setLoaderEnabled(true); + }); + engine.start('--video-driver', video_driver).then(function() { + setStatusMode('hidden'); + initializing = false; + }); + }).catch(displayFailureNotice); + } + }; + document.getElementById("startButton").onclick = function() { + preloadZip(document.getElementById('zip-file')).then(function(zip) { + startEditor(zip); + }); + } + + function preloadZip(target) { + return new Promise(function(resolve, reject) { + if (target.files.length > 0) { + target.files[0].arrayBuffer().then(function(data) { + resolve(data); + }); + } else { + resolve(); + } + }); + } + //]]></script> +</body> +</html> diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index a8861124b9..7381ea13b7 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -9,6 +9,7 @@ javascript_files = [ "javascript_eval.cpp", "javascript_main.cpp", "os_javascript.cpp", + "api/javascript_tools_editor_plugin.cpp", ] build_targets = ["#bin/godot${PROGSUFFIX}.js", "#bin/godot${PROGSUFFIX}.wasm"] @@ -55,7 +56,7 @@ out_files = [ zip_dir.File(binary_name + ".wasm"), zip_dir.File(binary_name + ".html"), ] -html_file = "#misc/dist/html/full-size.html" +html_file = "#misc/dist/html/editor.html" if env["tools"] else "#misc/dist/html/full-size.html" in_files = [js_wrapped, build[1], html_file] if env["threads_enabled"]: in_files.append(build[2]) diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp index 9c73e5c4c4..aa0206d144 100644 --- a/platform/javascript/api/api.cpp +++ b/platform/javascript/api/api.cpp @@ -31,10 +31,12 @@ #include "api.h" #include "core/engine.h" #include "javascript_eval.h" +#include "javascript_tools_editor_plugin.h" static JavaScript *javascript_eval; void register_javascript_api() { + JavaScriptToolsEditorPlugin::initialize(); ClassDB::register_virtual_class<JavaScript>(); javascript_eval = memnew(JavaScript); Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp new file mode 100644 index 0000000000..e487bf23b7 --- /dev/null +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -0,0 +1,153 @@ +/*************************************************************************/ +/* javascript_tools_editor_plugin.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. */ +/*************************************************************************/ + +#if defined(TOOLS_ENABLED) && defined(JAVASCRIPT_ENABLED) +#include "javascript_tools_editor_plugin.h" + +#include "core/engine.h" +#include "core/os/dir_access.h" +#include "core/os/file_access.h" +#include "core/project_settings.h" +#include "editor/editor_node.h" + +#include <emscripten/emscripten.h> + +static void _javascript_editor_init_callback() { + EditorNode::get_singleton()->add_editor_plugin(memnew(JavaScriptToolsEditorPlugin(EditorNode::get_singleton()))); +} + +void JavaScriptToolsEditorPlugin::initialize() { + EditorNode::add_init_callback(_javascript_editor_init_callback); +} + +JavaScriptToolsEditorPlugin::JavaScriptToolsEditorPlugin(EditorNode *p_editor) { + Variant v; + add_tool_menu_item("Download Project Source", this, "_download_zip", v); +} + +void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { + if (!Engine::get_singleton() || !Engine::get_singleton()->is_editor_hint()) { + WARN_PRINT("Project download is only available in Editor mode"); + return; + } + String resource_path = ProjectSettings::get_singleton()->get_resource_path(); + + FileAccess *src_f; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + zipFile zip = zipOpen2("/tmp/project.zip", APPEND_STATUS_CREATE, NULL, &io); + String base_path = resource_path.substr(0, resource_path.rfind("/")) + "/"; + _zip_recursive(resource_path, base_path, zip); + zipClose(zip, NULL); + EM_ASM({ + const path = "/tmp/project.zip"; + 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: "application/zip" }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = "project.zip"; + a.style.display = "none"; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + }); +} + +void JavaScriptToolsEditorPlugin::_bind_methods() { + ClassDB::bind_method("_download_zip", &JavaScriptToolsEditorPlugin::_download_zip); +} + +void JavaScriptToolsEditorPlugin::_zip_file(String p_path, String p_base_path, zipFile p_zip) { + FileAccess *f = FileAccess::open(p_path, FileAccess::READ); + if (!f) { + WARN_PRINT("Unable to open file for zipping: " + p_path); + return; + } + Vector<uint8_t> data; + int len = f->get_len(); + data.resize(len); + f->get_buffer(data.ptrw(), len); + f->close(); + memdelete(f); + + String path = p_path.replace_first(p_base_path, ""); + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + NULL, + NULL, + 0, + NULL, + 0, + NULL, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipWriteInFileInZip(p_zip, data.ptr(), data.size()); + zipCloseFileInZip(p_zip); +} + +void JavaScriptToolsEditorPlugin::_zip_recursive(String p_path, String p_base_path, zipFile p_zip) { + DirAccess *dir = DirAccess::open(p_path); + if (!dir) { + WARN_PRINT("Unable to open dir for zipping: " + p_path); + return; + } + dir->list_dir_begin(); + String cur = dir->get_next(); + while (!cur.empty()) { + String cs = p_path.plus_file(cur); + if (cur == "." || cur == ".." || cur == ".import") { + // Skip + } else if (dir->current_is_dir()) { + String path = cs.replace_first(p_base_path, "") + "/"; + zipOpenNewFileInZip(p_zip, + path.utf8().get_data(), + NULL, + NULL, + 0, + NULL, + 0, + NULL, + Z_DEFLATED, + Z_DEFAULT_COMPRESSION); + zipCloseFileInZip(p_zip); + _zip_recursive(cs, p_base_path, p_zip); + } else { + _zip_file(cs, p_base_path, p_zip); + } + cur = dir->get_next(); + } +} +#endif diff --git a/platform/javascript/api/javascript_tools_editor_plugin.h b/platform/javascript/api/javascript_tools_editor_plugin.h new file mode 100644 index 0000000000..cc09fa4cd3 --- /dev/null +++ b/platform/javascript/api/javascript_tools_editor_plugin.h @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* javascript_tools_editor_plugin.h */ +/*************************************************************************/ +/* 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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H +#define JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H + +#if defined(TOOLS_ENABLED) && defined(JAVASCRIPT_ENABLED) +#include "core/io/zip_io.h" +#include "editor/editor_plugin.h" + +class JavaScriptToolsEditorPlugin : public EditorPlugin { + GDCLASS(JavaScriptToolsEditorPlugin, EditorPlugin); + +private: + void _zip_file(String p_path, String p_base_path, zipFile p_zip); + void _zip_recursive(String p_path, String p_base_path, zipFile p_zip); + +protected: + static void _bind_methods(); + + void _download_zip(Variant p_v); + +public: + static void initialize(); + + JavaScriptToolsEditorPlugin(EditorNode *p_editor); +}; +#else +class JavaScriptToolsEditorPlugin { +public: + static void initialize() {} +}; +#endif + +#endif // JAVASCRIPT_TOOLS_EDITOR_PLUGIN_H diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 4b5890545f..8f2961b33d 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -135,7 +135,7 @@ def configure(env): env.Append(CPPDEFINES=["PTHREAD_NO_RENAME"]) env.Append(CCFLAGS=["-s", "USE_PTHREADS=1"]) env.Append(LINKFLAGS=["-s", "USE_PTHREADS=1"]) - env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=4"]) + env.Append(LINKFLAGS=["-s", "PTHREAD_POOL_SIZE=8"]) env.Append(LINKFLAGS=["-s", "WASM_MEM_MAX=2048MB"]) env.extra_suffix = ".threads" + env.extra_suffix else: diff --git a/platform/javascript/engine/engine.js b/platform/javascript/engine/engine.js index adcd919a6b..05a11701c0 100644 --- a/platform/javascript/engine/engine.js +++ b/platform/javascript/engine/engine.js @@ -121,6 +121,7 @@ Function('return this')()['Engine'] = (function() { me.rtenv['noExitRuntime'] = true; me.rtenv['onExecute'] = me.onExecute; me.rtenv['onExit'] = function(code) { + me.rtenv['deinitFS'](); if (me.onExit) me.onExit(code); me.rtenv = null; @@ -227,6 +228,12 @@ Function('return this')()['Engine'] = (function() { this.persistentPaths = persistentPaths; }; + Engine.prototype.requestQuit = function() { + if (this.rtenv) { + this.rtenv['request_quit'](); + } + }; + // Closure compiler exported engine methods. /** @export */ Engine['isWebGLAvailable'] = Utils.isWebGLAvailable; @@ -249,5 +256,6 @@ Function('return this')()['Engine'] = (function() { Engine.prototype['setOnExit'] = Engine.prototype.setOnExit; Engine.prototype['copyToFS'] = Engine.prototype.copyToFS; Engine.prototype['setPersistentPaths'] = Engine.prototype.setPersistentPaths; + Engine.prototype['requestQuit'] = Engine.prototype.requestQuit; return Engine; })(); diff --git a/platform/javascript/native/utils.js b/platform/javascript/native/utils.js index 0b3698fd86..8d0beba454 100644 --- a/platform/javascript/native/utils.js +++ b/platform/javascript/native/utils.js @@ -29,8 +29,7 @@ /*************************************************************************/ Module['initFS'] = function(persistentPaths) { - FS.mkdir('/userfs'); - FS.mount(IDBFS, {}, '/userfs'); + Module.mount_points = ['/userfs'].concat(persistentPaths); function createRecursive(dir) { try { @@ -43,13 +42,14 @@ Module['initFS'] = function(persistentPaths) { } } - persistentPaths.forEach(function(path) { + Module.mount_points.forEach(function(path) { createRecursive(path); FS.mount(IDBFS, {}, path); }); return new Promise(function(resolve, reject) { FS.syncfs(true, function(err) { if (err) { + Module.mount_points = []; Module.idbfs = false; console.log("IndexedDB not available: " + err.message); } else { @@ -60,6 +60,21 @@ Module['initFS'] = function(persistentPaths) { }); }; +Module['deinitFS'] = function() { + Module.mount_points.forEach(function(path) { + try { + FS.unmount(path); + } catch (e) { + console.log("Already unmounted", e); + } + if (Module.idbfs && IDBFS.dbs[path]) { + IDBFS.dbs[path].close(); + delete IDBFS.dbs[path]; + } + }); + Module.mount_points = []; +}; + Module['copyToFS'] = function(path, buffer) { var p = path.lastIndexOf("/"); var dir = "/"; |