[HTML5] Preloader fetch, streaming instantiation.
This commit is contained in:
parent
8a020a6573
commit
f64ec5f1ad
|
@ -227,10 +227,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
||||||
/**
|
/**
|
||||||
* @ignore
|
* @ignore
|
||||||
* @param {string} loadPath
|
* @param {string} loadPath
|
||||||
* @param {Promise} loadPromise
|
* @param {Response} response
|
||||||
*/
|
*/
|
||||||
Config.prototype.getModuleConfig = function (loadPath, loadPromise) {
|
Config.prototype.getModuleConfig = function (loadPath, response) {
|
||||||
let loader = loadPromise;
|
let r = response;
|
||||||
return {
|
return {
|
||||||
'print': this.onPrint,
|
'print': this.onPrint,
|
||||||
'printErr': this.onPrintError,
|
'printErr': this.onPrintError,
|
||||||
|
@ -238,12 +238,17 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
||||||
'noExitRuntime': true,
|
'noExitRuntime': true,
|
||||||
'dynamicLibraries': [`${loadPath}.side.wasm`],
|
'dynamicLibraries': [`${loadPath}.side.wasm`],
|
||||||
'instantiateWasm': function (imports, onSuccess) {
|
'instantiateWasm': function (imports, onSuccess) {
|
||||||
loader.then(function (xhr) {
|
function done(result) {
|
||||||
WebAssembly.instantiate(xhr.response, imports).then(function (result) {
|
onSuccess(result['instance'], result['module']);
|
||||||
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 {};
|
return {};
|
||||||
},
|
},
|
||||||
'locateFile': function (path) {
|
'locateFile': function (path) {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
var Godot;
|
var Godot;
|
||||||
var WebAssembly = {};
|
var WebAssembly = {};
|
||||||
WebAssembly.instantiate = function(buffer, imports) {};
|
WebAssembly.instantiate = function(buffer, imports) {};
|
||||||
|
WebAssembly.instantiateStreaming = function(response, imports) {};
|
||||||
|
|
|
@ -42,7 +42,7 @@ const Engine = (function () {
|
||||||
Engine.load = function (basePath) {
|
Engine.load = function (basePath) {
|
||||||
if (loadPromise == null) {
|
if (loadPromise == null) {
|
||||||
loadPath = basePath;
|
loadPath = basePath;
|
||||||
loadPromise = preloader.loadPromise(`${loadPath}.wasm`);
|
loadPromise = preloader.loadPromise(`${loadPath}.wasm`, true);
|
||||||
requestAnimationFrame(preloader.animateProgress);
|
requestAnimationFrame(preloader.animateProgress);
|
||||||
}
|
}
|
||||||
return loadPromise;
|
return loadPromise;
|
||||||
|
@ -98,21 +98,25 @@ const Engine = (function () {
|
||||||
}
|
}
|
||||||
Engine.load(basePath);
|
Engine.load(basePath);
|
||||||
}
|
}
|
||||||
preloader.setProgressFunc(this.config.onProgress);
|
|
||||||
let config = this.config.getModuleConfig(loadPath, loadPromise);
|
|
||||||
const me = this;
|
const me = this;
|
||||||
initPromise = new Promise(function (resolve, reject) {
|
function doInit(promise) {
|
||||||
Godot(config).then(function (module) {
|
return promise.then(function (response) {
|
||||||
module['initFS'](me.config.persistentPaths).then(function (fs_err) {
|
return Godot(me.config.getModuleConfig(loadPath, response.clone()));
|
||||||
me.rtenv = module;
|
}).then(function (module) {
|
||||||
if (me.config.unloadAfterInit) {
|
const paths = me.config.persistentPaths;
|
||||||
Engine.unload();
|
return module['initFS'](paths).then(function (err) {
|
||||||
}
|
return Promise.resolve(module);
|
||||||
resolve();
|
|
||||||
config = null;
|
|
||||||
});
|
});
|
||||||
|
}).then(function (module) {
|
||||||
|
me.rtenv = module;
|
||||||
|
if (me.config.unloadAfterInit) {
|
||||||
|
Engine.unload();
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
preloader.setProgressFunc(this.config.onProgress);
|
||||||
|
initPromise = doInit(loadPromise);
|
||||||
return initPromise;
|
return initPromise;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,54 +1,79 @@
|
||||||
const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
|
const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
|
||||||
const loadXHR = function (resolve, reject, file, tracker, attempts) {
|
function getTrackedResponse(response, load_status) {
|
||||||
const xhr = new XMLHttpRequest();
|
let clen = 0;
|
||||||
|
let compressed = false;
|
||||||
|
response.headers.forEach(function (value, header) {
|
||||||
|
const h = header.toLowerCase().trim();
|
||||||
|
// We can't accurately compute compressed stream length.
|
||||||
|
if (h === 'content-encoding') {
|
||||||
|
compressed = true;
|
||||||
|
} else if (h === 'content-length') {
|
||||||
|
const length = parseInt(value, 10);
|
||||||
|
if (!Number.isNaN(length) && length > 0) {
|
||||||
|
clen = length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!compressed && clen) {
|
||||||
|
load_status.total = clen;
|
||||||
|
}
|
||||||
|
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, raw) {
|
||||||
tracker[file] = {
|
tracker[file] = {
|
||||||
total: 0,
|
total: 0,
|
||||||
loaded: 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) {
|
if (attempts <= 1) {
|
||||||
reject(new Error(`Failed loading file '${file}'`));
|
return Promise.reject(err);
|
||||||
} else {
|
}
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
loadXHR(resolve, reject, file, tracker, attempts - 1);
|
retry(func, attempts - 1).then(resolve).catch(reject);
|
||||||
}, 1000);
|
}, 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 DOWNLOAD_ATTEMPTS_MAX = 4;
|
||||||
const loadingFiles = {};
|
const loadingFiles = {};
|
||||||
|
@ -63,7 +88,7 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
|
||||||
|
|
||||||
Object.keys(loadingFiles).forEach(function (file) {
|
Object.keys(loadingFiles).forEach(function (file) {
|
||||||
const stat = loadingFiles[file];
|
const stat = loadingFiles[file];
|
||||||
if (!stat.final) {
|
if (!stat.done) {
|
||||||
progressIsFinal = false;
|
progressIsFinal = false;
|
||||||
}
|
}
|
||||||
if (!totalIsValid || stat.total === 0) {
|
if (!totalIsValid || stat.total === 0) {
|
||||||
|
@ -92,10 +117,8 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
|
||||||
progressFunc = callback;
|
progressFunc = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.loadPromise = function (file) {
|
this.loadPromise = function (file, raw = false) {
|
||||||
return new Promise(function (resolve, reject) {
|
return retry(loadFetch.bind(null, file, loadingFiles, raw), DOWNLOAD_ATTEMPTS_MAX);
|
||||||
loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.preloadedFiles = [];
|
this.preloadedFiles = [];
|
||||||
|
@ -103,10 +126,10 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
|
||||||
let buffer = null;
|
let buffer = null;
|
||||||
if (typeof pathOrBuffer === 'string') {
|
if (typeof pathOrBuffer === 'string') {
|
||||||
const me = this;
|
const me = this;
|
||||||
return this.loadPromise(pathOrBuffer).then(function (xhr) {
|
return this.loadPromise(pathOrBuffer).then(function (buf) {
|
||||||
me.preloadedFiles.push({
|
me.preloadedFiles.push({
|
||||||
path: destPath || pathOrBuffer,
|
path: destPath || pathOrBuffer,
|
||||||
buffer: xhr.response,
|
buffer: buf,
|
||||||
});
|
});
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue