diff options
Diffstat (limited to 'platform/javascript')
-rw-r--r-- | platform/javascript/SCsub | 37 | ||||
-rw-r--r-- | platform/javascript/audio_driver_javascript.cpp | 79 | ||||
-rw-r--r-- | platform/javascript/audio_driver_javascript.h | 16 | ||||
-rw-r--r-- | platform/javascript/detect.py | 25 | ||||
-rw-r--r-- | platform/javascript/engine.js | 366 | ||||
-rw-r--r-- | platform/javascript/export/export.cpp | 30 | ||||
-rw-r--r-- | platform/javascript/godot_shell.html | 347 | ||||
-rw-r--r-- | platform/javascript/javascript_eval.cpp | 82 | ||||
-rw-r--r-- | platform/javascript/javascript_main.cpp | 16 | ||||
-rw-r--r-- | platform/javascript/os_javascript.cpp | 80 | ||||
-rw-r--r-- | platform/javascript/os_javascript.h | 13 | ||||
-rw-r--r-- | platform/javascript/power_javascript.cpp | 6 | ||||
-rw-r--r-- | platform/javascript/power_javascript.h | 6 | ||||
-rw-r--r-- | platform/javascript/pre_asmjs.js | 3 | ||||
-rw-r--r-- | platform/javascript/pre_wasm.js | 3 |
15 files changed, 618 insertions, 491 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index b804863ee1..f01d9367d2 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -20,33 +20,32 @@ for x in javascript_files: javascript_objects.append(env_javascript.Object(x)) env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""]) -env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"']) # output file name without file extension basename = "godot" + env["PROGSUFFIX"] target_dir = env.Dir("#bin") -js_file = target_dir.File(basename + ".js") -implicit_targets = [js_file] zip_dir = target_dir.Dir('.javascript_zip') -zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"]) - -if env['wasm'] == 'yes': - wasm_file = target_dir.File(basename+'.wasm') - implicit_targets.append(wasm_file) - zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file)) +zip_files = env.InstallAs(zip_dir.File('godot.html'), '#misc/dist/html/default.html') + +implicit_targets = [] +if env['wasm']: + wasm = target_dir.File(basename + '.wasm') + implicit_targets.append(wasm) + zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm)) + prejs = env.File('pre_wasm.js') else: - asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')] - zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + asmjs_files = [target_dir.File(basename + '.asm.js'), target_dir.File(basename + '.js.mem')] implicit_targets.extend(asmjs_files) + zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) + prejs = env.File('pre_asmjs.js') -# HTML file must be the first target in the list -html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0] -Depends(html_file, "godot_shell.html") +js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0]; +zip_files.append(InstallAs(zip_dir.File('godot.js'), js)) -# Emscripten hardcodes file names, so replace common base name with -# placeholder while leaving extension; also change `.html.mem` to just `.mem` -fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html') +postjs = env.File('engine.js') +env.Depends(js, [prejs, postjs]) +env.Append(LINKFLAGS=['--pre-js', prejs.path]) +env.Append(LINKFLAGS=['--post-js', postjs.path]) -zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html)) -Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") +Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 4c0e5fd966..cd3974669f 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -29,6 +29,7 @@ /*************************************************************************/ #include "audio_driver_javascript.h" +#include <emscripten.h> #include <string.h> #define MAX_NUMBER_INTERFACES 3 @@ -38,22 +39,91 @@ //AudioDriverJavaScript* AudioDriverJavaScript::s_ad=NULL; +AudioDriverJavaScript *AudioDriverJavaScript::singleton_js = NULL; const char *AudioDriverJavaScript::get_name() const { return "JavaScript"; } +extern "C" { + +void js_audio_driver_mix_function(int p_frames) { + + //print_line("MIXI! "+itos(p_frames)); + AudioDriverJavaScript::singleton_js->mix_to_js(p_frames); +} +} + +void AudioDriverJavaScript::mix_to_js(int p_frames) { + + int todo = p_frames; + int offset = 0; + + while (todo) { + + int tomix = MIN(todo, INTERNAL_BUFFER_SIZE); + + audio_server_process(p_frames, stream_buffer); + for (int i = 0; i < tomix * internal_buffer_channels; i++) { + internal_buffer[i] = float(stream_buffer[i] >> 16) * 32768.0; + } + + /* clang-format off */ + EM_ASM_({ + var data = HEAPF32.subarray($0 / 4, $0 / 4 + $2 * 2); + + for (var channel = 0; channel < _as_output_buffer.numberOfChannels; channel++) { + var outputData = _as_output_buffer.getChannelData(channel); + // Loop through samples + for (var sample = 0; sample < $2; sample++) { + // make output equal to the same as the input + outputData[sample + $1] = data[sample * 2 + channel]; + } + } + }, internal_buffer, offset, tomix); + /* clang-format on */ + + todo -= tomix; + offset += tomix; + } +} + Error AudioDriverJavaScript::init() { return OK; } void AudioDriverJavaScript::start() { + + internal_buffer_channels = 2; + internal_buffer = memnew_arr(float, INTERNAL_BUFFER_SIZE *internal_buffer_channels); + stream_buffer = memnew_arr(int32_t, INTERNAL_BUFFER_SIZE * 4); //max 4 channels + + /* clang-format off */ + EM_ASM( + _as_audioctx = new (window.AudioContext || window.webkitAudioContext)(); + + audio_server_mix_function = Module.cwrap('js_audio_driver_mix_function', 'void', ['number']); + ); + + int buffer_latency = 16384; + EM_ASM_( { + _as_script_node = _as_audioctx.createScriptProcessor($0, 0, 2); + _as_script_node.connect(_as_audioctx.destination); + console.log(_as_script_node.bufferSize); + + _as_script_node.onaudioprocess = function(audioProcessingEvent) { + // The output buffer contains the samples that will be modified and played + _as_output_buffer = audioProcessingEvent.outputBuffer; + audio_server_mix_function(_as_output_buffer.getChannelData(0).length); + } + }, buffer_latency); + /* clang-format on */ } int AudioDriverJavaScript::get_mix_rate() const { - return 44100; + return mix_rate; } AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { @@ -63,7 +133,7 @@ AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { void AudioDriverJavaScript::lock() { - /* + /*no locking, as threads are not supported if (active && mutex) mutex->lock(); */ @@ -71,7 +141,7 @@ void AudioDriverJavaScript::lock() { void AudioDriverJavaScript::unlock() { - /* + /*no locking, as threads are not supported if (active && mutex) mutex->unlock(); */ @@ -81,4 +151,7 @@ void AudioDriverJavaScript::finish() { } AudioDriverJavaScript::AudioDriverJavaScript() { + + mix_rate = 44100; + singleton_js = this; } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index c5cebe800f..c3adeca07b 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -35,7 +35,23 @@ #include "os/mutex.h" class AudioDriverJavaScript : public AudioDriver { + + enum { + INTERNAL_BUFFER_SIZE = 4096, + STREAM_SCALE_BITS = 12 + + }; + + int mix_rate; + float *internal_buffer; + int internal_buffer_channels; + int internal_buffer_size; + int32_t *stream_buffer; + public: + void mix_to_js(int p_frames); + static AudioDriverJavaScript *singleton_js; + virtual const char *get_name() const; virtual Error init(); diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index 5f066f1b15..a2988d9c60 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -13,22 +13,22 @@ def get_name(): def can_build(): - return ("EMSCRIPTEN_ROOT" in os.environ) + return ("EMSCRIPTEN_ROOT" in os.environ or "EMSCRIPTEN" in os.environ) def get_opts(): - + from SCons.Variables import BoolVariable return [ - ['wasm', 'Compile to WebAssembly', 'no'], - ['javascript_eval', 'Enable JavaScript eval interface', 'yes'], + BoolVariable('wasm', 'Compile to WebAssembly', False), + BoolVariable('javascript_eval', 'Enable JavaScript eval interface', True), ] def get_flags(): return [ - ('tools', 'no'), - ('module_theora_enabled', 'no'), + ('tools', False), + ('module_theora_enabled', False), ] @@ -66,7 +66,10 @@ def configure(env): ## Compiler configuration env['ENV'] = os.environ - env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT']) + if ("EMSCRIPTEN_ROOT" in os.environ): + env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT']) + elif ("EMSCRIPTEN" in os.environ): + env.PrependENVPath('PATH', os.environ['EMSCRIPTEN']) env['CC'] = 'emcc' env['CXX'] = 'em++' env['LINK'] = 'emcc' @@ -95,14 +98,15 @@ def configure(env): # These flags help keep the file size down env.Append(CPPFLAGS=["-fno-exceptions", '-DNO_SAFE_CAST', '-fno-rtti']) - if env['javascript_eval'] == 'yes': + if env['javascript_eval']: env.Append(CPPFLAGS=['-DJAVASCRIPT_EVAL_ENABLED']) ## Link flags + env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"']) env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1']) - if (env['wasm'] == 'yes'): + if env['wasm']: env.Append(LINKFLAGS=['-s', 'BINARYEN=1']) # In contrast to asm.js, enabling memory growth on WebAssembly has no # major performance impact, and causes only a negligible increase in @@ -112,7 +116,8 @@ def configure(env): else: env.Append(LINKFLAGS=['-s', 'ASM_JS=1']) env.Append(LINKFLAGS=['--separate-asm']) + env.Append(LINKFLAGS=['--memory-init-file', '1']) # TODO: Move that to opus module's config - if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"): + if 'module_opus_enabled' in env and env['module_opus_enabled']: env.opus_fixed_point = "yes" diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js new file mode 100644 index 0000000000..99d1c20bbd --- /dev/null +++ b/platform/javascript/engine.js @@ -0,0 +1,366 @@ + return Module; + }, +}; + +(function() { + var engine = Engine; + + var USING_WASM = engine.USING_WASM; + var DOWNLOAD_ATTEMPTS_MAX = 4; + + var basePath = null; + var engineLoadPromise = null; + + var loadingFiles = {}; + + function getBasePath(path) { + + if (path.endsWith('/')) + path = path.slice(0, -1); + if (path.lastIndexOf('.') > path.lastIndexOf('/')) + path = path.slice(0, path.lastIndexOf('.')); + return path; + } + + function getBaseName(path) { + + path = getBasePath(path); + return path.slice(path.lastIndexOf('/') + 1); + } + + Engine = function Engine() { + + this.rtenv = null; + + var gameInitPromise = null; + var unloadAfterInit = true; + var memorySize = 268435456; + + var progressFunc = null; + var pckProgressTracker = {}; + var lastProgress = { loaded: 0, total: 0 }; + + var canvas = null; + var stdout = null; + var stderr = null; + + this.initGame = function(mainPack) { + + if (!gameInitPromise) { + + if (mainPack === undefined) { + if (basePath !== null) { + mainPack = basePath + '.pck'; + } else { + return Promise.reject(new Error("No main pack to load specified")); + } + } + if (basePath === null) + basePath = getBasePath(mainPack); + + gameInitPromise = Engine.initEngine().then( + instantiate.bind(this) + ); + var gameLoadPromise = loadPromise(mainPack, pckProgressTracker).then(function(xhr) { return xhr.response; }); + gameInitPromise = Promise.all([gameLoadPromise, gameInitPromise]).then(function(values) { + // resolve with pck + return new Uint8Array(values[0]); + }); + if (unloadAfterInit) + gameInitPromise.then(Engine.unloadEngine); + requestAnimationFrame(animateProgress); + } + return gameInitPromise; + }; + + function instantiate(initializer) { + + var rtenvOpts = { + noInitialRun: true, + thisProgram: getBaseName(basePath), + engine: this, + }; + if (typeof stdout === 'function') + rtenvOpts.print = stdout; + if (typeof stderr === 'function') + rtenvOpts.printErr = stderr; + if (typeof WebAssembly === 'object' && initializer instanceof ArrayBuffer) { + rtenvOpts.instantiateWasm = function(imports, onSuccess) { + WebAssembly.instantiate(initializer, imports).then(function(result) { + onSuccess(result.instance); + }); + return {}; + }; + } else if (initializer.asm && initializer.mem) { + rtenvOpts.asm = initializer.asm; + rtenvOpts.memoryInitializerRequest = initializer.mem; + rtenvOpts.TOTAL_MEMORY = memorySize; + } else { + throw new Error("Invalid initializer"); + } + + return new Promise(function(resolve, reject) { + rtenvOpts.onRuntimeInitialized = resolve; + rtenvOpts.onAbort = reject; + rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts); + }); + } + + this.start = function(mainPack) { + + return this.initGame(mainPack).then(synchronousStart.bind(this)); + }; + + function synchronousStart(pckView) { + // TODO don't expect canvas when runninng as cli tool + if (canvas instanceof HTMLCanvasElement) { + this.rtenv.canvas = canvas; + } else { + var firstCanvas = document.getElementsByTagName('canvas')[0]; + if (firstCanvas instanceof HTMLCanvasElement) { + this.rtenv.canvas = firstCanvas; + } else { + throw new Error("No canvas found"); + } + } + + var actualCanvas = this.rtenv.canvas; + var context = false; + try { + context = actualCanvas.getContext('webgl2') || actualCanvas.getContext('experimental-webgl2'); + } catch (e) {} + if (!context) { + throw new Error("WebGL 2 not available"); + } + + // canvas can grab focus on click + if (actualCanvas.tabIndex < 0) { + actualCanvas.tabIndex = 0; + } + // necessary to calculate cursor coordinates correctly + actualCanvas.style.padding = 0; + actualCanvas.style.borderWidth = 0; + actualCanvas.style.borderStyle = 'none'; + // until context restoration is implemented + actualCanvas.addEventListener('webglcontextlost', function(ev) { + alert("WebGL context lost, please reload the page"); + ev.preventDefault(); + }, false); + + this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true); + gameInitPromise = null; + this.rtenv.callMain(); + } + + this.setProgressFunc = function(func) { + progressFunc = func; + }; + + function animateProgress() { + + var loaded = 0; + var total = 0; + var totalIsValid = true; + var progressIsFinal = true; + + [loadingFiles, pckProgressTracker].forEach(function(tracker) { + Object.keys(tracker).forEach(function(file) { + if (!tracker[file].final) + progressIsFinal = false; + if (!totalIsValid || tracker[file].total === 0) { + totalIsValid = false; + total = 0; + } else { + total += tracker[file].total; + } + loaded += tracker[file].loaded; + }); + }); + if (loaded !== lastProgress.loaded || total !== lastProgress.total) { + lastProgress.loaded = loaded; + lastProgress.total = total; + if (typeof progressFunc === 'function') + progressFunc(loaded, total); + } + if (!progressIsFinal) + requestAnimationFrame(animateProgress); + } + + this.setCanvas = function(elem) { + canvas = elem; + }; + + this.setAsmjsMemorySize = function(size) { + memorySize = size; + }; + + this.setUnloadAfterInit = function(enabled) { + + if (enabled && !unloadAfterInit && gameInitPromise) { + gameInitPromise.then(Engine.unloadEngine); + } + unloadAfterInit = enabled; + }; + + this.setStdoutFunc = function(func) { + + var print = function(text) { + if (arguments.length > 1) { + text = Array.prototype.slice.call(arguments).join(" "); + } + func(text); + }; + if (this.rtenv) + this.rtenv.print = print; + stdout = print; + }; + + this.setStderrFunc = function(func) { + + var printErr = function(text) { + if (arguments.length > 1) + text = Array.prototype.slice.call(arguments).join(" "); + func(text); + }; + if (this.rtenv) + this.rtenv.printErr = printErr; + stderr = printErr; + }; + + + }; // Engine() + + Engine.RuntimeEnvironment = engine.RuntimeEnvironment; + + Engine.initEngine = function(newBasePath) { + + if (newBasePath !== undefined) basePath = getBasePath(newBasePath); + if (engineLoadPromise === null) { + if (USING_WASM) { + if (typeof WebAssembly !== 'object') + return Promise.reject(new Error("Browser doesn't support WebAssembly")); + // TODO cache/retrieve module to/from idb + engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) { + return xhr.response; + }); + } else { + var asmjsPromise = loadPromise(basePath + '.asm.js').then(function(xhr) { + return asmjsModulePromise(xhr.response); + }); + var memPromise = loadPromise(basePath + '.mem'); + engineLoadPromise = Promise.all([asmjsPromise, memPromise]).then(function(values) { + return { asm: values[0], mem: values[1] }; + }); + } + engineLoadPromise = engineLoadPromise.catch(function(err) { + engineLoadPromise = null; + throw err; + }); + } + return engineLoadPromise; + }; + + function asmjsModulePromise(module) { + var elem = document.createElement('script'); + var script = new Blob([ + 'Engine.asm = (function() { var Module = {};', + module, + 'return Module.asm; })();' + ]); + var url = URL.createObjectURL(script); + elem.src = url; + return new Promise(function(resolve, reject) { + elem.addEventListener('load', function() { + URL.revokeObjectURL(url); + var asm = Engine.asm; + Engine.asm = undefined; + setTimeout(function() { + // delay to reclaim compilation memory + resolve(asm); + }, 1); + }); + elem.addEventListener('error', function() { + URL.revokeObjectURL(url); + reject("asm.js faiilure"); + }); + document.body.appendChild(elem); + }); + } + + Engine.unloadEngine = function() { + engineLoadPromise = null; + }; + + function loadPromise(file, tracker) { + if (tracker === undefined) + tracker = loadingFiles; + return new Promise(function(resolve, reject) { + loadXHR(resolve, reject, file, tracker); + }); + } + + function loadXHR(resolve, reject, file, tracker) { + + var xhr = new XMLHttpRequest; + xhr.open('GET', file); + if (!file.endsWith('.js')) { + xhr.responseType = 'arraybuffer'; + } + ['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) { + xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); + }); + xhr.send(); + } + + function onXHREvent(resolve, reject, file, tracker, ev) { + + if (this.status >= 400) { + + if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + reject(new Error("Failed loading file '" + file + "': " + this.statusText)); + this.abort(); + return; + } else { + loadXHR(resolve, reject, file); + } + } + + switch (ev.type) { + case 'loadstart': + if (tracker[file] === undefined) { + tracker[file] = { + total: ev.total, + loaded: ev.loaded, + attempts: 0, + final: false, + }; + } + break; + + case 'progress': + tracker[file].loaded = ev.loaded; + tracker[file].total = ev.total; + break; + + case 'load': + tracker[file].final = true; + resolve(this); + break; + + case 'error': + case 'timeout': + if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { + tracker[file].final = true; + reject(new Error("Failed loading file '" + file + "'")); + } else { + loadXHR(resolve, reject, file); + } + break; + + case 'abort': + tracker[file].final = true; + reject(new Error("Loading file '" + file + "' was aborted.")); + break; + } + } +})(); diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 5a161dd0ab..4a97bf4c32 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -100,8 +100,8 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; - current_line = current_line.replace("$GODOT_TMEM", itos(memory_mb * 1024 * 1024)); - current_line = current_line.replace("$GODOT_BASE", p_name); + current_line = current_line.replace("$GODOT_TOTAL_MEMORY", itos(memory_mb * 1024 * 1024)); + current_line = current_line.replace("$GODOT_BASENAME", p_name); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); str_export += current_line + "\n"; @@ -114,28 +114,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re } } -void EditorExportPlatformJavaScript::_fix_fsloader_js(Vector<uint8_t> &p_js, const String &p_pack_name, uint64_t p_pack_size) { - - String str_template = String::utf8(reinterpret_cast<const char *>(p_js.ptr()), p_js.size()); - String str_export; - Vector<String> lines = str_template.split("\n"); - for (int i = 0; i < lines.size(); i++) { - if (lines[i].find("$GODOT_PACK_NAME") != -1) { - str_export += lines[i].replace("$GODOT_PACK_NAME", p_pack_name); - } else if (lines[i].find("$GODOT_PACK_SIZE") != -1) { - str_export += lines[i].replace("$GODOT_PACK_SIZE", itos(p_pack_size)); - } else { - str_export += lines[i] + "\n"; - } - } - - CharString cs = str_export.utf8(); - p_js.resize(cs.length()); - for (int i = 0; i < cs.length(); i++) { - p_js[i] = cs[i]; - } -} - void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) { if (p_preset->get("texture_format/s3tc")) { @@ -286,10 +264,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug); file = p_path.get_file(); - } else if (file == "godotfs.js") { - - _fix_fsloader_js(data, pck_path.get_file(), pack_size); - file = p_path.get_file().get_basename() + "fs.js"; } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; diff --git a/platform/javascript/godot_shell.html b/platform/javascript/godot_shell.html deleted file mode 100644 index ee7399a129..0000000000 --- a/platform/javascript/godot_shell.html +++ /dev/null @@ -1,347 +0,0 @@ -<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> -<head> - <meta charset="utf-8" /> - <title></title> - <style type="text/css"> - body { - margin: 0; - border: 0 none; - padding: 0; - text-align: center; - background-color: #222226; - font-family: 'Droid Sans', Arial, sans-serif; - } - - - /* Godot Engine default theme style - * ================================ */ - - .godot { - color: #e0e0e0; - background-color: #3b3943; - background-image: linear-gradient(to bottom, #403e48, #35333c); - border: 1px solid #45434e; - box-shadow: 0 0 1px 1px #2f2d35; - } - - button.godot { - font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */ - padding: 1px 5px; - background-color: #37353f; - background-image: linear-gradient(to bottom, #413e49, #3a3842); - border: 1px solid #514f5d; - border-radius: 1px; - box-shadow: 0 0 1px 1px #2a2930; - } - - button.godot:hover { - color: #f0f0f0; - background-color: #44414e; - background-image: linear-gradient(to bottom, #494652, #423f4c); - border: 1px solid #5a5667; - box-shadow: 0 0 1px 1px #26252b; - } - - button.godot:active { - color: #fff; - background-color: #3e3b46; - background-image: linear-gradient(to bottom, #36343d, #413e49); - border: 1px solid #4f4c59; - box-shadow: 0 0 1px 1px #26252b; - } - - button.godot:disabled { - color: rgba(230, 230, 230, 0.2); - background-color: #3d3d3d; - background-image: linear-gradient(to bottom, #434343, #393939); - border: 1px solid #474747; - box-shadow: 0 0 1px 1px #2d2b33; - } - - - /* Canvas / wrapper - * ================ */ - - #container { - display: inline-block; /* scale with canvas */ - vertical-align: top; /* prevent extra height */ - position: relative; /* root for absolutely positioned overlay */ - margin: 0; - border: 0 none; - padding: 0; - background-color: #0c0c0c; - } - - #canvas { - display: block; - margin: 0 auto; - /* canvas must have border and padding set to zero to - * calculate cursor coordinates correctly */ - border: 0 none; - padding: 0; - color: white; - } - - #canvas:focus { - outline: none; - } - - - /* Status display - * ============== */ - - #status-container { - 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 { - line-height: 1.3; - cursor: pointer; - visibility: visible; - padding: 4px 6px; - } - - - /* Debug output - * ============ */ - - #output-panel { - display: none; - max-width: 700px; - font-size: small; - margin: 6px auto 0; - padding: 0 4px 4px; - text-align: left; - line-height: 2.2; - } - - #output-header { - display: flex; - justify-content: space-between; - align-items: center; - } - - #output-container { - padding: 6px; - background-color: #2c2a32; - box-shadow: inset 0 0 1px 1px #232127; - color: #bbb; - } - - #output-scroll { - line-height: 1; - height: 12em; - overflow-y: scroll; - white-space: pre-wrap; - font-size: small; - font-family: "Lucida Console", Monaco, monospace; - } - </style> -$GODOT_HEAD_INCLUDE -</head> -<body> - <div id="container"> - <canvas id="canvas" width="640" height="480" tabindex="0" oncontextmenu="event.preventDefault();"> - HTML5 canvas appears to be unsupported in the current browser.<br /> - Please try updating or use a different browser. - </canvas> - <div id="status-container"> - <span id="status" class="godot" onclick="this.style.visibility='hidden';">Downloading page...</span> - </div> - </div> - <div id="output-panel" class="godot"> - <div id="output-header"> - Output: - <button class="godot" type="button" autocomplete="off" onclick="Presentation.clearOutput();">Clear</button> - </div> - <div id="output-container"><div id="output-scroll"></div></div> - </div> - - <!-- Scripts --> - <script type="text/javascript">//<![CDATA[ - var Presentation = (function() { - var statusElement = document.getElementById("status"); - var canvasElement = document.getElementById("canvas"); - - var presentation = { - postRun: [], - setStatusVisible: function setStatusVisible(visible) { - statusElement.style.visibility = (visible ? "visible" : "hidden"); - }, - setStatus: function setStatus(text) { - if (text.length === 0) { - // emscripten sets empty string as status after "Running..." - // per timeout, but another status may have been set by then - if (Presentation.setStatus.lastText === "Running...") - Presentation.setStatusVisible(false); - return; - } - Presentation.setStatus.lastText = text; - while (statusElement.lastChild) { - statusElement.removeChild(statusElement.lastChild); - } - var lines = text.split("\n"); - lines.forEach(function(line, index) { - statusElement.appendChild(document.createTextNode(line)); - statusElement.appendChild(document.createElement("br")); - }); - var closeNote = document.createElement("span"); - closeNote.style.fontSize = "small"; - closeNote.textContent = "click to close"; - statusElement.appendChild(closeNote); - Presentation.setStatusVisible(true); - }, - isWebGL2Available: function isWebGL2Available() { - var context; - try { - context = canvasElement.getContext("webgl2") || canvasElement.getContext("experimental-webgl2"); - } catch (e) {} - return !!context; - }, - }; - - window.onerror = function(event) { presentation.setStatus("Failure during start-up\nSee JavaScript console") }; - - if ($GODOT_DEBUG_ENABLED) { // debugging enabled - var outputRoot = document.getElementById("output-panel"); - var outputElement = document.getElementById("output-scroll"); - const maxOutputMessages = 400; - - presentation.setOutputVisible = function setOutputVisible(visible) { - outputRoot.style.display = (visible ? "block" : "none"); - }; - presentation.clearOutput = function clearOutput() { - while (outputElement.firstChild) { - outputElement.firstChild.remove(); - } - }; - - presentation.setOutputVisible(true); - - presentation.print = function print(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - if (text.length <= 0) return; - while (outputElement.childElementCount >= maxOutputMessages) { - outputElement.firstChild.remove(); - } - var msg = document.createElement("div"); - if (String.prototype.trim.call(text).startsWith("**ERROR**") - || String.prototype.trim.call(text).startsWith("**EXCEPTION**")) { - msg.style.color = "#d44"; - } else if (String.prototype.trim.call(text).startsWith("**WARNING**")) { - msg.style.color = "#ccc000"; - } else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) { - msg.style.color = "#c6d"; - } - msg.textContent = text; - var scrollToBottom = outputElement.scrollHeight - (outputElement.clientHeight + outputElement.scrollTop) < 10; - outputElement.appendChild(msg); - if (scrollToBottom) { - outputElement.scrollTop = outputElement.scrollHeight; - } - }; - - presentation.postRun.push(function() { - window.onerror = function(event) { presentation.print("**EXCEPTION**:", event) }; - }); - - } else { - presentation.postRun.push(function() { window.onerror = null; }); - } - - return presentation; - })(); - - // Emscripten interface - var Module = (function() { - const BASE_NAME = '$GODOT_BASE'; - var module = { - thisProgram: BASE_NAME, - wasmBinaryFile: BASE_NAME + '.wasm', - TOTAL_MEMORY: $GODOT_TMEM, - print: function print(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - console.log(text); - if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") { - Presentation.print(text); - } - }, - printErr: function printErr(text) { - if (arguments.length > 1) { - text = Array.prototype.slice.call(arguments).join(" "); - } - console.error(text); - if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") { - Presentation.print("**ERROR**:", text) - } - }, - canvas: document.getElementById("canvas"), - setStatus: function setStatus(text) { - var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); - var now = Date.now(); - if (m) { - if (now - Date.now() < 30) // if this is a progress update, skip it if too soon - return; - text = m[1]; - } - if (typeof Presentation !== "undefined" && typeof Presentation.setStatus == "function") { - Presentation.setStatus(text); - } - } - }; - - // As a default initial behavior, pop up an alert when WebGL context is lost. To make your - // application robust, you may want to override this behavior before shipping! - // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 - module.canvas.addEventListener("webglcontextlost", function(e) { alert("WebGL context lost. Plase reload the page."); e.preventDefault(); }, false); - - if (typeof Presentation !== "undefined" && Presentation.postRun instanceof Array) { - module.postRun = Presentation.postRun; - } - - return module; - })(); - - if (!Presentation.isWebGL2Available()) { - Presentation.setStatus("WebGL 2 appears to be unsupported.\nPlease update browser and drivers."); - Presentation.preventLoading = true; - } else { - Presentation.setStatus("Downloading..."); - } - - if (Presentation.preventLoading) { - // prevent *fs.js and Emscripten's SCRIPT placeholder from loading any files - Presentation._XHR_send = XMLHttpRequest.prototype.send; - XMLHttpRequest.prototype.send = function() {}; - Presentation._Node_appendChild = Node.prototype.appendChild; - Node.prototype.appendChild = function(node) { - if (!(node instanceof HTMLScriptElement)) { - return Presentation._Node_appendChild.call(this, node); - } - } - } - //]]></script> - <script type="text/javascript" src="$GODOT_BASEfs.js"></script> -{{{ SCRIPT }}} - <script type="text/javascript"> - if (Presentation.preventLoading) { - XMLHttpRequest.prototype.send = Presentation._XHR_send; - Node.prototype.appendChild = Presentation._Node_appendChild; - } - </script> -</body> -</html> diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp index 74f8d80a76..1d737879f6 100644 --- a/platform/javascript/javascript_eval.cpp +++ b/platform/javascript/javascript_eval.cpp @@ -39,24 +39,41 @@ JavaScript *JavaScript::get_singleton() { return singleton; } +extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_poolbytearray_and_open_write(PoolByteArray *p_arr, PoolByteArray::Write *r_write, int p_len) { + + p_arr->resize(p_len); + *r_write = p_arr->write(); + return r_write->ptr(); +} + Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { union { - int i; + bool b; double d; char *s; } js_data[4]; + + PoolByteArray arr; + PoolByteArray::Write arr_write; + /* clang-format off */ Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({ + const CODE = $0; + const USE_GLOBAL_EXEC_CONTEXT = $1; + const PTR = $2; + const ELEM_LEN = $3; + const BYTEARRAY_PTR = $4; + const BYTEARRAY_WRITE_PTR = $5; var eval_ret; try { - if ($3) { // p_use_global_exec_context + if (USE_GLOBAL_EXEC_CONTEXT) { // indirect eval call grants global execution context var global_eval = eval; - eval_ret = global_eval(UTF8ToString($2)); + eval_ret = global_eval(UTF8ToString(CODE)); } else { - eval_ret = eval(UTF8ToString($2)); + eval_ret = eval(UTF8ToString(CODE)); } } catch (e) { Module.printErr(e); @@ -66,16 +83,11 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { switch (typeof eval_ret) { case 'boolean': - // bitwise op yields 32-bit int - setValue($0, eval_ret|0, 'i32'); + setValue(PTR, eval_ret, 'i32'); return 1; // BOOL case 'number': - if ((eval_ret|0)===eval_ret) { - setValue($0, eval_ret|0, 'i32'); - return 2; // INT - } - setValue($0, eval_ret, 'double'); + setValue(PTR, eval_ret, 'double'); return 3; // REAL case 'string': @@ -85,7 +97,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { if (array_ptr===0) { throw new Error('String allocation failed (probably out of memory)'); } - setValue($0, array_ptr|0 , '*'); + setValue(PTR, array_ptr , '*'); stringToUTF8(eval_ret, array_ptr, array_len); return 4; // STRING } catch (e) { @@ -102,41 +114,50 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { break; } - else if (typeof eval_ret.x==='number' && typeof eval_ret.y==='number') { - setValue($0, eval_ret.x, 'double'); - setValue($0+$1, eval_ret.y, 'double'); + 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) { + var bytes_ptr = ccall('resize_poolbytearray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + + if (typeof eval_ret.x==='number' && typeof eval_ret.y==='number') { + setValue(PTR, eval_ret.x, 'double'); + setValue(PTR + ELEM_LEN, eval_ret.y, 'double'); if (typeof eval_ret.z==='number') { - setValue($0+$1*2, eval_ret.z, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.z, 'double'); return 7; // VECTOR3 } else if (typeof eval_ret.width==='number' && typeof eval_ret.height==='number') { - setValue($0+$1*2, eval_ret.width, 'double'); - setValue($0+$1*3, eval_ret.height, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.width, 'double'); + setValue(PTR + ELEM_LEN*3, eval_ret.height, 'double'); return 6; // RECT2 } return 5; // VECTOR2 } - else if (typeof eval_ret.r==='number' && typeof eval_ret.g==='number' && typeof eval_ret.b==='number') { - // assume 8-bit rgb components since we're on the web - setValue($0, eval_ret.r, 'double'); - setValue($0+$1, eval_ret.g, 'double'); - setValue($0+$1*2, eval_ret.b, 'double'); - setValue($0+$1*3, typeof eval_ret.a==='number' ? eval_ret.a : 1, 'double'); + if (typeof eval_ret.r === 'number' && typeof eval_ret.g === 'number' && typeof eval_ret.b === 'number') { + setValue(PTR, eval_ret.r, 'double'); + setValue(PTR + ELEM_LEN, eval_ret.g, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.b, 'double'); + setValue(PTR + ELEM_LEN*3, typeof eval_ret.a === 'number' ? eval_ret.a : 1, 'double'); return 14; // COLOR } break; } return 0; // NIL - }, js_data, sizeof *js_data, p_code.utf8().get_data(), p_use_global_exec_context)); + }, p_code.utf8().get_data(), p_use_global_exec_context, js_data, sizeof *js_data, &arr, &arr_write)); /* clang-format on */ switch (return_type) { case Variant::BOOL: - return !!js_data->i; - case Variant::INT: - return js_data->i; + return js_data->b; case Variant::REAL: return js_data->d; case Variant::STRING: { @@ -153,7 +174,10 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { case Variant::RECT2: return Rect2(js_data[0].d, js_data[1].d, js_data[2].d, js_data[3].d); case Variant::COLOR: - return Color(js_data[0].d / 255., js_data[1].d / 255., js_data[2].d / 255., js_data[3].d); + return Color(js_data[0].d, js_data[1].d, js_data[2].d, js_data[3].d); + case Variant::POOL_BYTE_ARRAY: + arr_write = PoolByteArray::Write(); + return arr; } return Variant(); } diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index 4c948bf181..ed4f416cfd 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -39,8 +39,13 @@ static void main_loop() { os->main_loop_iterate(); } -extern "C" void main_after_fs_sync() { +extern "C" void main_after_fs_sync(char *p_idbfs_err) { + String idbfs_err = String::utf8(p_idbfs_err); + if (!idbfs_err.empty()) { + print_line("IndexedDB not available: " + idbfs_err); + } + os->set_idbfs_available(idbfs_err.empty()); // Ease up compatibility ResourceLoader::set_abort_on_missing_resources(false); Main::start(); @@ -60,14 +65,7 @@ int main(int argc, char *argv[]) { FS.mkdir('/userfs'); FS.mount(IDBFS, {}, '/userfs'); FS.syncfs(true, function(err) { - if (err) { - Module.setStatus('Failed to load persistent data\nPlease allow (third-party) cookies'); - Module.printErr('Failed to populate IDB file system: ' + err.message); - Module.noExitRuntime = false; - } else { - Module.print('Successfully populated IDB file system'); - ccall('main_after_fs_sync', null); - } + Module['ccall']('main_after_fs_sync', null, ['string'], [err ? err.message : ""]) }); ); /* clang-format on */ diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index a0e5610591..f6446e77da 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -85,6 +85,10 @@ void OS_JavaScript::initialize_core() { FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix> >(FileAccess::ACCESS_RESOURCES); } +void OS_JavaScript::initialize_logger() { + _set_logger(memnew(StdLogger)); +} + void OS_JavaScript::set_opengl_extensions(const char *p_gl_extensions) { ERR_FAIL_COND(!p_gl_extensions); @@ -172,14 +176,14 @@ static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent if (!is_canvas_focused()) { focus_canvas(); } - mask |= 1 << ev->get_button_index(); - } else if (mask & (1 << ev->get_button_index())) { - mask &= ~(1 << ev->get_button_index()); + mask |= ev->get_button_index(); + } else if (mask & ev->get_button_index()) { + mask &= ~ev->get_button_index(); } else { // release event, but press was outside the canvas, so ignore return false; } - ev->set_button_mask(mask >> 1); + ev->set_button_mask(mask); _input->parse_input_event(ev); // prevent selection dragging @@ -200,7 +204,7 @@ static EM_BOOL _mousemove_callback(int event_type, const EmscriptenMouseEvent *m Ref<InputEventMouseMotion> ev; ev.instance(); dom2godot_mod(mouse_event, ev); - ev->set_button_mask(input_mask >> 1); + ev->set_button_mask(input_mask); ev->set_position(pos); ev->set_global_position(ev->get_position()); @@ -227,7 +231,7 @@ static EM_BOOL _wheel_callback(int event_type, const EmscriptenWheelEvent *wheel Ref<InputEventMouseButton> ev; ev.instance(); - ev->set_button_mask(_input->get_mouse_button_mask() >> 1); + ev->set_button_mask(_input->get_mouse_button_mask()); ev->set_position(_input->get_mouse_position()); ev->set_global_position(ev->get_position()); @@ -291,7 +295,7 @@ static EM_BOOL _touchpress_callback(int event_type, const EmscriptenTouchEvent * Ref<InputEventMouseButton> ev_mouse; ev_mouse.instance(); - ev_mouse->set_button_mask(_input->get_mouse_button_mask() >> 1); + ev_mouse->set_button_mask(_input->get_mouse_button_mask()); dom2godot_mod(touch_event, ev_mouse); const EmscriptenTouchPoint &first_touch = touch_event->touches[lowest_id_index]; @@ -334,7 +338,7 @@ static EM_BOOL _touchmove_callback(int event_type, const EmscriptenTouchEvent *t Ref<InputEventMouseMotion> ev_mouse; ev_mouse.instance(); dom2godot_mod(touch_event, ev_mouse); - ev_mouse->set_button_mask(_input->get_mouse_button_mask() >> 1); + ev_mouse->set_button_mask(_input->get_mouse_button_mask()); const EmscriptenTouchPoint &first_touch = touch_event->touches[lowest_id_index]; ev_mouse->set_position(Point2(first_touch.canvasX, first_touch.canvasY)); @@ -464,11 +468,7 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i print_line("Init Audio"); AudioDriverManager::add_driver(&audio_driver_javascript); - audio_driver_javascript.set_singleton(); - if (audio_driver_javascript.init() != OK) { - - ERR_PRINT("Initializing audio failed."); - } + AudioDriverManager::initialize(p_audio_driver); RasterizerGLES3::register_config(); RasterizerGLES3::make_current(); @@ -521,21 +521,6 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i #undef SET_EM_CALLBACK #undef EM_CHECK - /* clang-format off */ - EM_ASM_ARGS({ - const send_notification = Module.cwrap('send_notification', null, ['number']); - const notifs = arguments; - (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, i) { - Module.canvas.addEventListener(event, send_notification.bind(this, notifs[i])); - }); - }, - MainLoop::NOTIFICATION_WM_MOUSE_ENTER, - MainLoop::NOTIFICATION_WM_MOUSE_EXIT, - MainLoop::NOTIFICATION_WM_FOCUS_IN, - MainLoop::NOTIFICATION_WM_FOCUS_OUT - ); -/* clang-format on */ - #ifdef JAVASCRIPT_EVAL_ENABLED javascript_eval = memnew(JavaScript); ProjectSettings::get_singleton()->add_singleton(ProjectSettings::Singleton("JavaScript", javascript_eval)); @@ -818,13 +803,29 @@ void OS_JavaScript::main_loop_begin() { if (main_loop) main_loop->init(); + + /* clang-format off */ + EM_ASM_ARGS({ + const send_notification = Module.cwrap('send_notification', null, ['number']); + const notifs = arguments; + (['mouseover', 'mouseleave', 'focus', 'blur']).forEach(function(event, i) { + Module.canvas.addEventListener(event, send_notification.bind(null, notifs[i])); + }); + }, + MainLoop::NOTIFICATION_WM_MOUSE_ENTER, + MainLoop::NOTIFICATION_WM_MOUSE_EXIT, + MainLoop::NOTIFICATION_WM_FOCUS_IN, + MainLoop::NOTIFICATION_WM_FOCUS_OUT + ); + /* clang-format on */ } + bool OS_JavaScript::main_loop_iterate() { if (!main_loop) return false; - if (time_to_save_sync >= 0) { + if (idbfs_available && time_to_save_sync >= 0) { int64_t newtime = get_ticks_msec(); int64_t elapsed = newtime - last_sync_time; last_sync_time = newtime; @@ -914,10 +915,10 @@ String OS_JavaScript::get_executable_path() const { void OS_JavaScript::_close_notification_funcs(const String &p_file, int p_flags) { - print_line("close " + p_file + " flags " + itos(p_flags)); - if (p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) { - static_cast<OS_JavaScript *>(get_singleton())->last_sync_time = OS::get_singleton()->get_ticks_msec(); - static_cast<OS_JavaScript *>(get_singleton())->time_to_save_sync = 5000; //five seconds since last save + OS_JavaScript *os = static_cast<OS_JavaScript *>(get_singleton()); + if (os->idbfs_available && p_file.begins_with("/userfs") && p_flags & FileAccess::WRITE) { + os->last_sync_time = OS::get_singleton()->get_ticks_msec(); + os->time_to_save_sync = 5000; //five seconds since last save } } @@ -975,7 +976,7 @@ String OS_JavaScript::get_joy_guid(int p_device) const { return input->get_joy_guid_remapped(p_device); } -PowerState OS_JavaScript::get_power_state() { +OS::PowerState OS_JavaScript::get_power_state() { return power_manager->get_power_state(); } @@ -992,6 +993,16 @@ bool OS_JavaScript::_check_internal_feature_support(const String &p_feature) { return p_feature == "web" || p_feature == "s3tc"; // TODO check for these features really being available } +void OS_JavaScript::set_idbfs_available(bool p_idbfs_available) { + + idbfs_available = p_idbfs_available; +} + +bool OS_JavaScript::is_userfs_persistent() const { + + return idbfs_available; +} + OS_JavaScript::OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func) { set_cmdline(p_execpath, get_cmdline_args()); main_loop = NULL; @@ -1003,6 +1014,7 @@ OS_JavaScript::OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_d get_data_dir_func = p_get_data_dir_func; FileAccessUnix::close_notification_func = _close_notification_funcs; + idbfs_available = false; time_to_save_sync = -1; } diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index f78a3f2768..1c939d3fd5 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -49,6 +49,7 @@ typedef String (*GetDataDirFunc)(); class OS_JavaScript : public OS_Unix { + bool idbfs_available; int64_t time_to_save_sync; int64_t last_sync_time; @@ -92,6 +93,7 @@ public: virtual int get_audio_driver_count() const; virtual const char *get_audio_driver_name(int p_driver) const; + virtual void initialize_logger(); virtual void initialize_core(); virtual void initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); @@ -104,11 +106,6 @@ public: //static OS* get_singleton(); - virtual void print_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, ErrorType p_type) { - - OS::print_error(p_function, p_file, p_line, p_code, p_rationale, p_type); - } - virtual void alert(const String &p_alert, const String &p_title = "ALERT!"); virtual void set_mouse_mode(MouseMode p_mode); @@ -140,6 +137,8 @@ public: virtual bool can_draw() const; + virtual bool is_userfs_persistent() const; + virtual void set_cursor_shape(CursorShape p_shape); void main_loop_begin(); @@ -165,12 +164,14 @@ public: virtual String get_joy_guid(int p_device) const; bool joy_connection_changed(int p_type, const EmscriptenGamepadEvent *p_event); - virtual PowerState get_power_state(); + virtual OS::PowerState get_power_state(); virtual int get_power_seconds_left(); virtual int get_power_percent_left(); virtual bool _check_internal_feature_support(const String &p_feature); + void set_idbfs_available(bool p_idbfs_available); + OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func); ~OS_JavaScript(); }; diff --git a/platform/javascript/power_javascript.cpp b/platform/javascript/power_javascript.cpp index 3d54146595..10964502d4 100644 --- a/platform/javascript/power_javascript.cpp +++ b/platform/javascript/power_javascript.cpp @@ -36,12 +36,12 @@ bool PowerJavascript::UpdatePowerInfo() { return false; } -PowerState PowerJavascript::get_power_state() { +OS::PowerState PowerJavascript::get_power_state() { if (UpdatePowerInfo()) { return power_state; } else { WARN_PRINT("Power management is not implemented on this platform, defaulting to POWERSTATE_UNKNOWN"); - return POWERSTATE_UNKNOWN; + return OS::POWERSTATE_UNKNOWN; } } @@ -64,7 +64,7 @@ int PowerJavascript::get_power_percent_left() { } PowerJavascript::PowerJavascript() - : nsecs_left(-1), percent_left(-1), power_state(POWERSTATE_UNKNOWN) { + : nsecs_left(-1), percent_left(-1), power_state(OS::POWERSTATE_UNKNOWN) { } PowerJavascript::~PowerJavascript() { diff --git a/platform/javascript/power_javascript.h b/platform/javascript/power_javascript.h index 834d765557..8454c5d728 100644 --- a/platform/javascript/power_javascript.h +++ b/platform/javascript/power_javascript.h @@ -31,13 +31,13 @@ #ifndef PLATFORM_JAVASCRIPT_POWER_JAVASCRIPT_H_ #define PLATFORM_JAVASCRIPT_POWER_JAVASCRIPT_H_ -#include "os/power.h" +#include "os/os.h" class PowerJavascript { private: int nsecs_left; int percent_left; - PowerState power_state; + OS::PowerState power_state; bool UpdatePowerInfo(); @@ -45,7 +45,7 @@ public: PowerJavascript(); virtual ~PowerJavascript(); - PowerState get_power_state(); + OS::PowerState get_power_state(); int get_power_seconds_left(); int get_power_percent_left(); }; diff --git a/platform/javascript/pre_asmjs.js b/platform/javascript/pre_asmjs.js new file mode 100644 index 0000000000..3c497721b6 --- /dev/null +++ b/platform/javascript/pre_asmjs.js @@ -0,0 +1,3 @@ +var Engine = { + USING_WASM: false, + RuntimeEnvironment: function(Module) { diff --git a/platform/javascript/pre_wasm.js b/platform/javascript/pre_wasm.js new file mode 100644 index 0000000000..be4383c8c9 --- /dev/null +++ b/platform/javascript/pre_wasm.js @@ -0,0 +1,3 @@ +var Engine = { + USING_WASM: true, + RuntimeEnvironment: function(Module) { |