[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

View File

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

View File

@ -164,3 +164,6 @@ def configure(env):
# callMain for manual start, FS for preloading.
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() {
var unloadAfterInit = true;
var canvas = null;
var resizeCanvasOnStart = false;
var customLocale = 'en_US';
var wasmExt = '.wasm';
var preloader = new Preloader();
var loader = new Loader();
var rtenv = null;
var executableName = '';
var wasmExt = '.wasm';
var unloadAfterInit = true;
var loadPath = '';
var loadPromise = null;
var initPromise = null;
@ -33,31 +25,42 @@ Function('return this')()['Engine'] = (function() {
};
/** @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) {
if (initPromise) {
return initPromise;
}
if (!loadPromise) {
if (loadPromise == null) {
if (!basePath) {
initPromise = Promise.reject(new Error("A base path must be provided when calling `init` and the engine is not loaded."));
return initPromise;
}
load(basePath);
}
var config = {}
var config = {};
if (typeof stdout === 'function')
config.print = stdout;
if (typeof stderr === 'function')
config.printErr = stderr;
initPromise = loader.init(loadPromise, loadPath, config).then(function() {
return new Promise(function(resolve, reject) {
rtenv = loader.env;
var me = this;
initPromise = new Promise(function(resolve, reject) {
config['locateFile'] = Utils.createLocateRewrite(loadPath);
config['instantiateWasm'] = Utils.createInstantiatePromise(loadPromise);
Godot(config).then(function(module) {
me.rtenv = module;
if (unloadAfterInit) {
loadPromise = null;
unload();
}
resolve();
config = null;
});
});
return initPromise;
@ -76,33 +79,71 @@ Function('return this')()['Engine'] = (function() {
args.push(arguments[i]);
}
var me = this;
return new Promise(function(resolve, reject) {
return me.init().then(function() {
if (!(canvas instanceof HTMLCanvasElement)) {
canvas = Utils.findCanvas();
}
rtenv['locale'] = customLocale;
rtenv['canvas'] = canvas;
rtenv['thisProgram'] = executableName;
rtenv['resizeCanvasOnStart'] = resizeCanvasOnStart;
loader.start(preloader.preloadedFiles, args).then(function() {
loader = null;
initPromise = null;
resolve();
return me.init().then(function() {
if (!me.rtenv) {
reject(new Error('The engine must be initialized before it can be started'));
}
if (!(me.canvas instanceof HTMLCanvasElement)) {
me.canvas = Utils.findCanvas();
}
// Canvas can grab focus on click, or key events won't work.
if (me.canvas.tabIndex < 0) {
me.canvas.tabIndex = 0;
}
// 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.
executableName = execName;
this.executableName = execName;
var me = this;
return Promise.all([
this.init(execName),
this.preloadFile(mainPack, mainPack)
]).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) {
canvas = canvasElem;
this.canvas = canvasElem;
};
Engine.prototype.setCanvasResizedOnStart = function(enabled) {
resizeCanvasOnStart = enabled;
this.resizeCanvasOnStart = enabled;
};
Engine.prototype.setLocale = function(locale) {
customLocale = locale;
this.customLocale = locale;
};
Engine.prototype.setExecutableName = function(newName) {
executableName = newName;
this.executableName = newName;
};
Engine.prototype.setProgressFunc = function(func) {
progressFunc = func;
}
};
Engine.prototype.setStdoutFunc = function(func) {
var print = function(text) {
if (arguments.length > 1) {
text = Array.prototype.slice.call(arguments).join(" ");
}
func(text);
};
if (rtenv)
rtenv.print = print;
if (this.rtenv)
this.rtenv.print = print;
stdout = print;
};
Engine.prototype.setStderrFunc = function(func) {
var printErr = function(text) {
if (arguments.length > 1)
text = Array.prototype.slice.call(arguments).join(" ");
func(text);
};
if (rtenv)
rtenv.printErr = printErr;
if (this.rtenv)
this.rtenv.printErr = 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.
/** @export */
Engine['isWebGLAvailable'] = Utils.isWebGLAvailable;
Engine['load'] = load;
Engine['unload'] = unload;
Engine.prototype['init'] = Engine.prototype.init
Engine.prototype['preloadFile'] = Engine.prototype.preloadFile
Engine.prototype['start'] = Engine.prototype.start
Engine.prototype['startGame'] = Engine.prototype.startGame
Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension
Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit
Engine.prototype['setCanvas'] = Engine.prototype.setCanvas
Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart
Engine.prototype['setLocale'] = Engine.prototype.setLocale
Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName
Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc
Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc
Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc
Engine.prototype['init'] = Engine.prototype.init;
Engine.prototype['preloadFile'] = Engine.prototype.preloadFile;
Engine.prototype['start'] = Engine.prototype.start;
Engine.prototype['startGame'] = Engine.prototype.startGame;
Engine.prototype['setWebAssemblyFilenameExtension'] = Engine.prototype.setWebAssemblyFilenameExtension;
Engine.prototype['setUnloadAfterInit'] = Engine.prototype.setUnloadAfterInit;
Engine.prototype['setCanvas'] = Engine.prototype.setCanvas;
Engine.prototype['setCanvasResizedOnStart'] = Engine.prototype.setCanvasResizedOnStart;
Engine.prototype['setLocale'] = Engine.prototype.setLocale;
Engine.prototype['setExecutableName'] = Engine.prototype.setExecutableName;
Engine.prototype['setProgressFunc'] = Engine.prototype.setProgressFunc;
Engine.prototype['setStdoutFunc'] = Engine.prototype.setStdoutFunc;
Engine.prototype['setStderrFunc'] = Engine.prototype.setStderrFunc;
Engine.prototype['setOnExit'] = Engine.prototype.setOnExit;
Engine.prototype['copyToFS'] = Engine.prototype.copyToFS;
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();
});
}
};