summaryrefslogtreecommitdiff
path: root/platform/javascript/js/engine
diff options
context:
space:
mode:
Diffstat (limited to 'platform/javascript/js/engine')
-rw-r--r--platform/javascript/js/engine/config.js54
-rw-r--r--platform/javascript/js/engine/engine.externs.js1
-rw-r--r--platform/javascript/js/engine/engine.js41
-rw-r--r--platform/javascript/js/engine/preloader.js108
4 files changed, 129 insertions, 75 deletions
diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js
index bba20dc360..ba61b14eb7 100644
--- a/platform/javascript/js/engine/config.js
+++ b/platform/javascript/js/engine/config.js
@@ -91,16 +91,42 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
*/
args: [],
/**
+ * When enabled, the game canvas will automatically grab the focus when the engine starts.
+ *
+ * @memberof EngineConfig
+ * @type {boolean}
+ * @default
+ */
+ focusCanvas: true,
+ /**
+ * When enabled, this will turn on experimental virtual keyboard support on mobile.
+ *
+ * @memberof EngineConfig
+ * @type {boolean}
+ * @default
+ */
+ experimentalVK: false,
+ /**
* @ignore
* @type {Array.<string>}
*/
persistentPaths: ['/userfs'],
/**
* @ignore
+ * @type {boolean}
+ */
+ persistentDrops: false,
+ /**
+ * @ignore
* @type {Array.<string>}
*/
gdnativeLibs: [],
/**
+ * @ignore
+ * @type {Array.<string>}
+ */
+ fileSizes: [],
+ /**
* A callback function for handling Godot's ``OS.execute`` calls.
*
* This is for example used in the Web Editor template to switch between project manager and editor, and for running the game.
@@ -218,7 +244,11 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
this.locale = parse('locale', this.locale);
this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
this.persistentPaths = parse('persistentPaths', this.persistentPaths);
+ this.persistentDrops = parse('persistentDrops', this.persistentDrops);
+ this.experimentalVK = parse('experimentalVK', this.experimentalVK);
+ this.focusCanvas = parse('focusCanvas', this.focusCanvas);
this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
+ this.fileSizes = parse('fileSizes', this.fileSizes);
this.args = parse('args', this.args);
this.onExecute = parse('onExecute', this.onExecute);
this.onExit = parse('onExit', this.onExit);
@@ -227,10 +257,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
/**
* @ignore
* @param {string} loadPath
- * @param {Promise} loadPromise
+ * @param {Response} response
*/
- Config.prototype.getModuleConfig = function (loadPath, loadPromise) {
- let loader = loadPromise;
+ Config.prototype.getModuleConfig = function (loadPath, response) {
+ let r = response;
return {
'print': this.onPrint,
'printErr': this.onPrintError,
@@ -238,12 +268,17 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
'noExitRuntime': true,
'dynamicLibraries': [`${loadPath}.side.wasm`],
'instantiateWasm': function (imports, onSuccess) {
- loader.then(function (xhr) {
- WebAssembly.instantiate(xhr.response, imports).then(function (result) {
- onSuccess(result['instance'], result['module']);
+ function done(result) {
+ onSuccess(result['instance'], result['module']);
+ }
+ if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') {
+ WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done);
+ } else {
+ r.arrayBuffer().then(function (buffer) {
+ WebAssembly.instantiate(buffer, imports).then(done);
});
- });
- loader = null;
+ }
+ r = null;
return {};
},
'locateFile': function (path) {
@@ -296,6 +331,9 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
'canvas': this.canvas,
'canvasResizePolicy': this.canvasResizePolicy,
'locale': locale,
+ 'persistentDrops': this.persistentDrops,
+ 'virtualKeyboard': this.experimentalVK,
+ 'focusCanvas': this.focusCanvas,
'onExecute': this.onExecute,
'onExit': function (p_code) {
cleanup(); // We always need to call the cleanup callback to free memory.
diff --git a/platform/javascript/js/engine/engine.externs.js b/platform/javascript/js/engine/engine.externs.js
index 1a94dd15ec..35a66a93ae 100644
--- a/platform/javascript/js/engine/engine.externs.js
+++ b/platform/javascript/js/engine/engine.externs.js
@@ -1,3 +1,4 @@
var Godot;
var WebAssembly = {};
WebAssembly.instantiate = function(buffer, imports) {};
+WebAssembly.instantiateStreaming = function(response, imports) {};
diff --git a/platform/javascript/js/engine/engine.js b/platform/javascript/js/engine/engine.js
index c51955ed3d..17a8df9e29 100644
--- a/platform/javascript/js/engine/engine.js
+++ b/platform/javascript/js/engine/engine.js
@@ -35,14 +35,15 @@ const Engine = (function () {
* Load the engine from the specified base path.
*
* @param {string} basePath Base path of the engine to load.
+ * @param {number=} [size=0] The file size if known.
* @returns {Promise} A Promise that resolves once the engine is loaded.
*
* @function Engine.load
*/
- Engine.load = function (basePath) {
+ Engine.load = function (basePath, size) {
if (loadPromise == null) {
loadPath = basePath;
- loadPromise = preloader.loadPromise(`${loadPath}.wasm`);
+ loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true);
requestAnimationFrame(preloader.animateProgress);
}
return loadPromise;
@@ -96,23 +97,31 @@ const Engine = (function () {
initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
return initPromise;
}
- Engine.load(basePath);
+ Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
}
- preloader.setProgressFunc(this.config.onProgress);
- let config = this.config.getModuleConfig(loadPath, loadPromise);
const me = this;
- initPromise = new Promise(function (resolve, reject) {
- Godot(config).then(function (module) {
- module['initFS'](me.config.persistentPaths).then(function (fs_err) {
- me.rtenv = module;
- if (me.config.unloadAfterInit) {
- Engine.unload();
- }
- resolve();
- config = null;
+ function doInit(promise) {
+ // Care! Promise chaining is bogus with old emscripten versions.
+ // This caused a regression with the Mono build (which uses an older emscripten version).
+ // Make sure to test that when refactoring.
+ return new Promise(function (resolve, reject) {
+ promise.then(function (response) {
+ const cloned = new Response(response.clone().body, { 'headers': [['content-type', 'application/wasm']] });
+ Godot(me.config.getModuleConfig(loadPath, cloned)).then(function (module) {
+ const paths = me.config.persistentPaths;
+ module['initFS'](paths).then(function (err) {
+ me.rtenv = module;
+ if (me.config.unloadAfterInit) {
+ Engine.unload();
+ }
+ resolve();
+ });
+ });
});
});
- });
+ }
+ preloader.setProgressFunc(this.config.onProgress);
+ initPromise = doInit(loadPromise);
return initPromise;
},
@@ -133,7 +142,7 @@ const Engine = (function () {
* @returns {Promise} A Promise that resolves once the file is loaded.
*/
preloadFile: function (file, path) {
- return preloader.preload(file, path);
+ return preloader.preload(file, path, this.config.fileSizes[file]);
},
/**
diff --git a/platform/javascript/js/engine/preloader.js b/platform/javascript/js/engine/preloader.js
index ec34fb93f2..564c68d264 100644
--- a/platform/javascript/js/engine/preloader.js
+++ b/platform/javascript/js/engine/preloader.js
@@ -1,54 +1,62 @@
const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
- const loadXHR = function (resolve, reject, file, tracker, attempts) {
- const xhr = new XMLHttpRequest();
+ function getTrackedResponse(response, load_status) {
+ function onloadprogress(reader, controller) {
+ return reader.read().then(function (result) {
+ if (load_status.done) {
+ return Promise.resolve();
+ }
+ if (result.value) {
+ controller.enqueue(result.value);
+ load_status.loaded += result.value.length;
+ }
+ if (!result.done) {
+ return onloadprogress(reader, controller);
+ }
+ load_status.done = true;
+ return Promise.resolve();
+ });
+ }
+ const reader = response.body.getReader();
+ return new Response(new ReadableStream({
+ start: function (controller) {
+ onloadprogress(reader, controller).then(function () {
+ controller.close();
+ });
+ },
+ }), { headers: response.headers });
+ }
+
+ function loadFetch(file, tracker, fileSize, raw) {
tracker[file] = {
- total: 0,
+ total: fileSize || 0,
loaded: 0,
- final: false,
+ done: false,
};
- xhr.onerror = function () {
+ return fetch(file).then(function (response) {
+ if (!response.ok) {
+ return Promise.reject(new Error(`Failed loading file '${file}'`));
+ }
+ const tr = getTrackedResponse(response, tracker[file]);
+ if (raw) {
+ return Promise.resolve(tr);
+ }
+ return tr.arrayBuffer();
+ });
+ }
+
+ function retry(func, attempts = 1) {
+ function onerror(err) {
if (attempts <= 1) {
- reject(new Error(`Failed loading file '${file}'`));
- } else {
+ return Promise.reject(err);
+ }
+ return new Promise(function (resolve, reject) {
setTimeout(function () {
- loadXHR(resolve, reject, file, tracker, attempts - 1);
+ retry(func, attempts - 1).then(resolve).catch(reject);
}, 1000);
- }
- };
- xhr.onabort = function () {
- tracker[file].final = true;
- reject(new Error(`Loading file '${file}' was aborted.`));
- };
- xhr.onloadstart = function (ev) {
- tracker[file].total = ev.total;
- tracker[file].loaded = ev.loaded;
- };
- xhr.onprogress = function (ev) {
- tracker[file].loaded = ev.loaded;
- tracker[file].total = ev.total;
- };
- xhr.onload = function () {
- if (xhr.status >= 400) {
- if (xhr.status < 500 || attempts <= 1) {
- reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`));
- xhr.abort();
- } else {
- setTimeout(function () {
- loadXHR(resolve, reject, file, tracker, attempts - 1);
- }, 1000);
- }
- } else {
- tracker[file].final = true;
- resolve(xhr);
- }
- };
- // Make request.
- xhr.open('GET', file);
- if (!file.endsWith('.js')) {
- xhr.responseType = 'arraybuffer';
+ });
}
- xhr.send();
- };
+ return func().catch(onerror);
+ }
const DOWNLOAD_ATTEMPTS_MAX = 4;
const loadingFiles = {};
@@ -63,7 +71,7 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
Object.keys(loadingFiles).forEach(function (file) {
const stat = loadingFiles[file];
- if (!stat.final) {
+ if (!stat.done) {
progressIsFinal = false;
}
if (!totalIsValid || stat.total === 0) {
@@ -92,21 +100,19 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
progressFunc = callback;
};
- this.loadPromise = function (file) {
- return new Promise(function (resolve, reject) {
- loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX);
- });
+ this.loadPromise = function (file, fileSize, raw = false) {
+ return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX);
};
this.preloadedFiles = [];
- this.preload = function (pathOrBuffer, destPath) {
+ this.preload = function (pathOrBuffer, destPath, fileSize) {
let buffer = null;
if (typeof pathOrBuffer === 'string') {
const me = this;
- return this.loadPromise(pathOrBuffer).then(function (xhr) {
+ return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) {
me.preloadedFiles.push({
path: destPath || pathOrBuffer,
- buffer: xhr.response,
+ buffer: buf,
});
return Promise.resolve();
});