[HTML5] Locale, input fix, context, exit.

Add missing semicolumns in engine.js
Add optional extra args to JS Engine.startGame
Remove loader.js, explicit noExitRuntime.
Also add onExit callback (undocumented in emscripten)
This commit is contained in:
Fabio Alessandrelli 2020-05-01 14:36:41 +02:00
parent 6a0473bcc2
commit ee99cd42d5
4 changed files with 112 additions and 91 deletions
platform/javascript

View File

@ -32,7 +32,6 @@ env.Depends(build, js_modules)
engine = [ engine = [
"engine/preloader.js", "engine/preloader.js",
"engine/loader.js",
"engine/utils.js", "engine/utils.js",
"engine/engine.js", "engine/engine.js",
] ]

View File

@ -164,3 +164,6 @@ def configure(env):
# callMain for manual start, FS for preloading. # callMain for manual start, FS for preloading.
env.Append(LINKFLAGS=["-s", 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]']) env.Append(LINKFLAGS=["-s", 'EXTRA_EXPORTED_RUNTIME_METHODS=["callMain", "FS"]'])
# Add code that allow exiting runtime.
env.Append(LINKFLAGS=['-s', 'EXIT_RUNTIME=1'])

View File

@ -1,16 +1,8 @@
Function('return this')()['Engine'] = (function() { Function('return this')()['Engine'] = (function() {
var unloadAfterInit = true;
var canvas = null;
var resizeCanvasOnStart = false;
var customLocale = 'en_US';
var wasmExt = '.wasm';
var preloader = new Preloader(); var preloader = new Preloader();
var loader = new Loader();
var rtenv = null;
var executableName = ''; var wasmExt = '.wasm';
var unloadAfterInit = true;
var loadPath = ''; var loadPath = '';
var loadPromise = null; var loadPromise = null;
var initPromise = null; var initPromise = null;
@ -33,31 +25,42 @@ Function('return this')()['Engine'] = (function() {
}; };
/** @constructor */ /** @constructor */
function Engine() {}; function Engine() {
this.canvas = null;
this.executableName = '';
this.rtenv = null;
this.customLocale = null;
this.resizeCanvasOnStart = false;
this.onExit = null;
};
Engine.prototype.init = /** @param {string=} basePath */ function(basePath) { Engine.prototype.init = /** @param {string=} basePath */ function(basePath) {
if (initPromise) { if (initPromise) {
return initPromise; return initPromise;
} }
if (!loadPromise) { if (loadPromise == null) {
if (!basePath) { if (!basePath) {
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;
} }
load(basePath); load(basePath);
} }
var config = {} var config = {};
if (typeof stdout === 'function') if (typeof stdout === 'function')
config.print = stdout; config.print = stdout;
if (typeof stderr === 'function') if (typeof stderr === 'function')
config.printErr = stderr; config.printErr = stderr;
initPromise = loader.init(loadPromise, loadPath, config).then(function() { var me = this;
return new Promise(function(resolve, reject) { initPromise = new Promise(function(resolve, reject) {
rtenv = loader.env; config['locateFile'] = Utils.createLocateRewrite(loadPath);
config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
Godot(config).then(function(module) {
me.rtenv = module;
if (unloadAfterInit) { if (unloadAfterInit) {
loadPromise = null; unload();
} }
resolve(); resolve();
config = null;
}); });
}); });
return initPromise; return initPromise;
@ -76,33 +79,71 @@ Function('return this')()['Engine'] = (function() {
args.push(arguments[i]); args.push(arguments[i]);
} }
var me = this; var me = this;
return new Promise(function(resolve, reject) { return me.init().then(function() {
return me.init().then(function() { if (!me.rtenv) {
if (!(canvas instanceof HTMLCanvasElement)) { reject(new Error('The engine must be initialized before it can be started'));
canvas = Utils.findCanvas(); }
}
rtenv['locale'] = customLocale; if (!(me.canvas instanceof HTMLCanvasElement)) {
rtenv['canvas'] = canvas; me.canvas = Utils.findCanvas();
rtenv['thisProgram'] = executableName; }
rtenv['resizeCanvasOnStart'] = resizeCanvasOnStart;
loader.start(preloader.preloadedFiles, args).then(function() { // Canvas can grab focus on click, or key events won't work.
loader = null; if (me.canvas.tabIndex < 0) {
initPromise = null; me.canvas.tabIndex = 0;
resolve(); }
// Disable right-click context menu.
me.canvas.addEventListener('contextmenu', function(ev) {
ev.preventDefault();
}, false);
// Until context restoration is implemented warn the user of context loss.
me.canvas.addEventListener('webglcontextlost', function(ev) {
alert("WebGL context lost, please reload the page");
ev.preventDefault();
}, false);
// Browser locale, or custom one if defined.
var locale = me.customLocale;
if (!locale) {
locale = navigator.languages ? navigator.languages[0] : navigator.language;
locale = locale.split('.')[0];
}
me.rtenv['locale'] = locale;
me.rtenv['canvas'] = me.canvas;
me.rtenv['thisProgram'] = me.executableName;
me.rtenv['resizeCanvasOnStart'] = me.resizeCanvasOnStart;
me.rtenv['noExitRuntime'] = true;
me.rtenv['onExit'] = function(code) {
if (me.onExit)
me.onExit(code);
me.rtenv = null;
}
return new Promise(function(resolve, reject) {
preloader.preloadedFiles.forEach(function(file) {
Utils.copyToFS(me.rtenv['FS'], file.path, file.buffer);
}); });
preloader.preloadedFiles.length = 0; // Clear memory
me.rtenv['callMain'](args);
initPromise = null;
resolve();
}); });
}); });
}; };
Engine.prototype.startGame = function(execName, mainPack) { Engine.prototype.startGame = function(execName, mainPack, extraArgs) {
// Start and init with execName as loadPath if not inited. // Start and init with execName as loadPath if not inited.
executableName = execName; this.executableName = execName;
var me = this; var me = this;
return Promise.all([ return Promise.all([
this.init(execName), this.init(execName),
this.preloadFile(mainPack, mainPack) this.preloadFile(mainPack, mainPack)
]).then(function() { ]).then(function() {
return me.start('--main-pack', mainPack); var args = ['--main-pack', mainPack];
if (extraArgs)
args = args.concat(extraArgs);
return me.start.apply(me, args);
}); });
}; };
@ -118,67 +159,78 @@ Function('return this')()['Engine'] = (function() {
}; };
Engine.prototype.setCanvas = function(canvasElem) { Engine.prototype.setCanvas = function(canvasElem) {
canvas = canvasElem; this.canvas = canvasElem;
}; };
Engine.prototype.setCanvasResizedOnStart = function(enabled) { Engine.prototype.setCanvasResizedOnStart = function(enabled) {
resizeCanvasOnStart = enabled; this.resizeCanvasOnStart = enabled;
}; };
Engine.prototype.setLocale = function(locale) { Engine.prototype.setLocale = function(locale) {
customLocale = locale; this.customLocale = locale;
}; };
Engine.prototype.setExecutableName = function(newName) { Engine.prototype.setExecutableName = function(newName) {
executableName = newName; this.executableName = newName;
}; };
Engine.prototype.setProgressFunc = function(func) { Engine.prototype.setProgressFunc = function(func) {
progressFunc = func; progressFunc = func;
} };
Engine.prototype.setStdoutFunc = function(func) { Engine.prototype.setStdoutFunc = function(func) {
var print = function(text) { var print = function(text) {
if (arguments.length > 1) { if (arguments.length > 1) {
text = Array.prototype.slice.call(arguments).join(" "); text = Array.prototype.slice.call(arguments).join(" ");
} }
func(text); func(text);
}; };
if (rtenv) if (this.rtenv)
rtenv.print = print; this.rtenv.print = print;
stdout = print; stdout = print;
}; };
Engine.prototype.setStderrFunc = function(func) { Engine.prototype.setStderrFunc = function(func) {
var printErr = function(text) { var printErr = function(text) {
if (arguments.length > 1) if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(" "); text = Array.prototype.slice.call(arguments).join(" ");
func(text); func(text);
}; };
if (rtenv) if (this.rtenv)
rtenv.printErr = printErr; this.rtenv.printErr = printErr;
stderr = printErr; stderr = printErr;
}; };
Engine.prototype.setOnExit = function(onExit) {
this.onExit = onExit;
}
Engine.prototype.copyToFS = function(path, buffer) {
if (this.rtenv == null) {
throw new Error("Engine must be inited before copying files");
}
Utils.copyToFS(this.rtenv['FS'], path, buffer);
}
// Closure compiler exported engine methods. // Closure compiler exported engine methods.
/** @export */ /** @export */
Engine['isWebGLAvailable'] = Utils.isWebGLAvailable; Engine['isWebGLAvailable'] = Utils.isWebGLAvailable;
Engine['load'] = load; Engine['load'] = load;
Engine['unload'] = unload; Engine['unload'] = unload;
Engine.prototype['init'] = Engine.prototype.init Engine.prototype['init'] = Engine.prototype.init;
Engine.prototype['preloadFile'] = Engine.prototype.preloadFile Engine.prototype['preloadFile'] = Engine.prototype.preloadFile;
Engine.prototype['start'] = Engine.prototype.start Engine.prototype['start'] = Engine.prototype.start;
Engine.prototype['startGame'] = Engine.prototype.startGame Engine.prototype['startGame'] = Engine.prototype.startGame;
Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension;
Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit;
Engine.prototype['setCanvas'] = Engine.prototype.setCanvas Engine.prototype['setCanvas'] = Engine.prototype.setCanvas;
Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart;
Engine.prototype['setLocale'] = Engine.prototype.setLocale Engine.prototype['setLocale'] = Engine.prototype.setLocale;
Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName;
Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc;
Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc;
Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc;
Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
return Engine; return Engine;
})(); })();

View File

@ -1,33 +0,0 @@
var Loader = /** @constructor */ function() {
this.env = null;
this.init = function(loadPromise, basePath, config) {
var me = this;
return new Promise(function(resolve, reject) {
var cfg = config || {};
cfg['locateFile'] = Utils.createLocateRewrite(basePath);
cfg['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
loadPromise = null;
Godot(cfg).then(function(module) {
me.env = module;
resolve();
});
});
}
this.start = function(preloadedFiles, args) {
var me = this;
return new Promise(function(resolve, reject) {
if (!me.env) {
reject(new Error('The engine must be initialized before it can be started'));
}
preloadedFiles.forEach(function(file) {
Utils.copyToFS(me.env['FS'], file.path, file.buffer);
});
preloadedFiles.length = 0; // Clear memory
me.env['callMain'](args);
resolve();
});
}
};