[HTML5] Export process writes sizes in template.

This allow the loading bar to be much more reliable, even in cases where
realible stream loading status is not detectable (server-side
compression, chunked encoding).
This commit is contained in:
Fabio Alessandrelli 2021-03-01 17:53:56 +01:00
parent f64ec5f1ad
commit 41c64533b0
4 changed files with 47 additions and 27 deletions

View File

@ -242,7 +242,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
return name; return name;
} }
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects); void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
static void _server_thread_poll(void *data); static void _server_thread_poll(void *data);
@ -280,7 +280,7 @@ public:
~EditorExportPlatformJavaScript(); ~EditorExportPlatformJavaScript();
}; };
void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) { void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size()); String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
String str_export; String str_export;
@ -300,6 +300,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
config["gdnativeLibs"] = libs; config["gdnativeLibs"] = libs;
config["executable"] = p_name; config["executable"] = p_name;
config["args"] = args; config["args"] = args;
config["fileSizes"] = p_file_sizes;
const String str_config = JSON::print(config); const String str_config = JSON::print(config);
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
@ -481,6 +482,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
return ERR_FILE_CORRUPT; return ERR_FILE_CORRUPT;
} }
Vector<uint8_t> html;
Dictionary file_sizes;
do { do {
//get filename //get filename
unz_file_info info; unz_file_info info;
@ -489,6 +492,16 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
String file = fname; String file = fname;
// HTML is handled later
if (file == "godot.html") {
if (custom_html.empty()) {
html.resize(info.uncompressed_size);
unzOpenCurrentFile(pkg);
unzReadCurrentFile(pkg, html.ptrw(), html.size());
unzCloseCurrentFile(pkg);
}
continue;
}
Vector<uint8_t> data; Vector<uint8_t> data;
data.resize(info.uncompressed_size); data.resize(info.uncompressed_size);
@ -499,15 +512,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
//write //write
if (file == "godot.html") { if (file == "godot.js") {
if (!custom_html.empty()) {
continue;
}
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
file = p_path.get_file();
} else if (file == "godot.js") {
file = p_path.get_file().get_basename() + ".js"; file = p_path.get_file().get_basename() + ".js";
} else if (file == "godot.worker.js") { } else if (file == "godot.worker.js") {
@ -525,6 +530,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
} else if (file == "godot.wasm") { } else if (file == "godot.wasm") {
file = p_path.get_file().get_basename() + ".wasm"; file = p_path.get_file().get_basename() + ".wasm";
file_sizes[file.get_file()] = (uint64_t)info.uncompressed_size;
} }
String dst = p_path.get_base_dir().plus_file(file); String dst = p_path.get_base_dir().plus_file(file);
@ -547,19 +553,26 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html); EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html);
return ERR_FILE_CANT_READ; return ERR_FILE_CANT_READ;
} }
Vector<uint8_t> buf; html.resize(f->get_len());
buf.resize(f->get_len()); f->get_buffer(html.ptrw(), html.size());
f->get_buffer(buf.ptrw(), buf.size());
memdelete(f); memdelete(f);
_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects); }
{
FileAccess *f = FileAccess::open(pck_path, FileAccess::READ);
if (f) {
file_sizes[pck_path.get_file()] = (uint64_t)f->get_len();
memdelete(f);
f = NULL;
}
_fix_html(html, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects, file_sizes);
f = FileAccess::open(p_path, FileAccess::WRITE); f = FileAccess::open(p_path, FileAccess::WRITE);
if (!f) { if (!f) {
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path); EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path);
return ERR_FILE_CANT_WRITE; return ERR_FILE_CANT_WRITE;
} }
f->store_buffer(buf.ptr(), buf.size()); f->store_buffer(html.ptr(), html.size());
memdelete(f); memdelete(f);
html.resize(0);
} }
Ref<Image> splash; Ref<Image> splash;

View File

@ -100,6 +100,11 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
* @type {Array.<string>} * @type {Array.<string>}
*/ */
gdnativeLibs: [], gdnativeLibs: [],
/**
* @ignore
* @type {Array.<string>}
*/
fileSizes: [],
/** /**
* A callback function for handling Godot's ``OS.execute`` calls. * A callback function for handling Godot's ``OS.execute`` calls.
* *
@ -219,6 +224,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy); this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
this.persistentPaths = parse('persistentPaths', this.persistentPaths); this.persistentPaths = parse('persistentPaths', this.persistentPaths);
this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs); this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
this.fileSizes = parse('fileSizes', this.fileSizes);
this.args = parse('args', this.args); this.args = parse('args', this.args);
this.onExecute = parse('onExecute', this.onExecute); this.onExecute = parse('onExecute', this.onExecute);
this.onExit = parse('onExit', this.onExit); this.onExit = parse('onExit', this.onExit);

View File

@ -35,14 +35,15 @@ const Engine = (function () {
* Load the engine from the specified base path. * Load the engine from the specified base path.
* *
* @param {string} basePath Base path of the engine to load. * @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. * @returns {Promise} A Promise that resolves once the engine is loaded.
* *
* @function Engine.load * @function Engine.load
*/ */
Engine.load = function (basePath) { Engine.load = function (basePath, size) {
if (loadPromise == null) { if (loadPromise == null) {
loadPath = basePath; loadPath = basePath;
loadPromise = preloader.loadPromise(`${loadPath}.wasm`, true); loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true);
requestAnimationFrame(preloader.animateProgress); requestAnimationFrame(preloader.animateProgress);
} }
return loadPromise; return loadPromise;
@ -96,7 +97,7 @@ const Engine = (function () {
initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.')); initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
return initPromise; return initPromise;
} }
Engine.load(basePath); Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
} }
const me = this; const me = this;
function doInit(promise) { function doInit(promise) {
@ -137,7 +138,7 @@ const Engine = (function () {
* @returns {Promise} A Promise that resolves once the file is loaded. * @returns {Promise} A Promise that resolves once the file is loaded.
*/ */
preloadFile: function (file, path) { preloadFile: function (file, path) {
return preloader.preload(file, path); return preloader.preload(file, path, this.config.fileSizes[file]);
}, },
/** /**

View File

@ -43,9 +43,9 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
}), { headers: response.headers }); }), { headers: response.headers });
} }
function loadFetch(file, tracker, raw) { function loadFetch(file, tracker, fileSize, raw) {
tracker[file] = { tracker[file] = {
total: 0, total: fileSize || 0,
loaded: 0, loaded: 0,
done: false, done: false,
}; };
@ -117,16 +117,16 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
progressFunc = callback; progressFunc = callback;
}; };
this.loadPromise = function (file, raw = false) { this.loadPromise = function (file, fileSize, raw = false) {
return retry(loadFetch.bind(null, file, loadingFiles, raw), DOWNLOAD_ATTEMPTS_MAX); return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX);
}; };
this.preloadedFiles = []; this.preloadedFiles = [];
this.preload = function (pathOrBuffer, destPath) { this.preload = function (pathOrBuffer, destPath, fileSize) {
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 (buf) { return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) {
me.preloadedFiles.push({ me.preloadedFiles.push({
path: destPath || pathOrBuffer, path: destPath || pathOrBuffer,
buffer: buf, buffer: buf,