Merge pull request #13044 from eska014/enginejs

Change HTML5 start-up API
This commit is contained in:
Rémi Verschelde 2017-11-19 20:18:00 +01:00 committed by GitHub
commit f0795ae2fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 137 additions and 94 deletions

View file

@ -225,7 +225,7 @@ $GODOT_HEAD_INCLUDE
<script type="text/javascript" src="$GODOT_BASENAME.js"></script> <script type="text/javascript" src="$GODOT_BASENAME.js"></script>
<script type="text/javascript">//<![CDATA[ <script type="text/javascript">//<![CDATA[
var game = new Engine; var engine = new Engine;
(function() { (function() {
@ -245,7 +245,7 @@ $GODOT_HEAD_INCLUDE
var indeterminiateStatusAnimationId = 0; var indeterminiateStatusAnimationId = 0;
setStatusMode('indeterminate'); setStatusMode('indeterminate');
game.setCanvas(canvas); engine.setCanvas(canvas);
function setStatusMode(mode) { function setStatusMode(mode) {
@ -300,7 +300,7 @@ $GODOT_HEAD_INCLUDE
}); });
}; };
game.setProgressFunc((current, total) => { engine.setProgressFunc((current, total) => {
if (total > 0) { if (total > 0) {
statusProgressInner.style.width = current/total * 100 + '%'; statusProgressInner.style.width = current/total * 100 + '%';
@ -330,10 +330,6 @@ $GODOT_HEAD_INCLUDE
outputRoot.style.display = 'block'; outputRoot.style.display = 'block';
function print(text) { function print(text) {
if (arguments.length > 1) {
text = Array.prototype.slice.call(arguments).join(" ");
}
if (text.length <= 0) return;
while (outputScroll.childElementCount >= OUTPUT_MSG_COUNT_MAX) { while (outputScroll.childElementCount >= OUTPUT_MSG_COUNT_MAX) {
outputScroll.firstChild.remove(); outputScroll.firstChild.remove();
} }
@ -354,26 +350,31 @@ $GODOT_HEAD_INCLUDE
}; };
function printError(text) { function printError(text) {
print('**ERROR**' + ":", text); if (!text.startsWith('**ERROR**: ')) {
text = '**ERROR**: ' + text;
}
print(text);
} }
game.setStdoutFunc(text => { engine.setStdoutFunc(text => {
print(text); print(text);
console.log(text); console.log(text);
}); });
game.setStderrFunc(text => { engine.setStderrFunc(text => {
printError(text); printError(text);
console.warn(text); console.warn(text);
}); });
} }
game.start(BASENAME + '.pck').then(() => { engine.startGame(BASENAME + '.pck').then(() => {
setStatusMode('hidden'); setStatusMode('hidden');
initializing = false; initializing = false;
}, err => { }, err => {
if (DEBUG_ENABLED) if (DEBUG_ENABLED) {
printError(err.message); printError(err.message);
console.warn(err);
}
setStatusNotice(err.message); setStatusNotice(err.message);
setStatusMode('notice'); setStatusMode('notice');
initializing = false; initializing = false;

View file

@ -102,14 +102,13 @@ def configure(env):
## Link flags ## Link flags
env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"'])
env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1'])
env.Append(LINKFLAGS=['-s', 'BINARYEN=1']) env.Append(LINKFLAGS=['-s', 'BINARYEN=1'])
# In contrast to asm.js, enabling memory growth on WebAssembly has no
# major performance impact, and causes only a negligible increase in
# memory size.
env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1']) env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1'])
env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1'])
env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"'])
env.Append(LINKFLAGS=['-s', 'INVOKE_RUN=0'])
env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1'])
# TODO: Move that to opus module's config # TODO: Move that to opus module's config
if 'module_opus_enabled' in env and env['module_opus_enabled']: if 'module_opus_enabled' in env and env['module_opus_enabled']:

View file

@ -31,82 +31,101 @@
this.rtenv = null; this.rtenv = null;
var gameInitPromise = null; var initPromise = null;
var unloadAfterInit = true; var unloadAfterInit = true;
var preloadedFiles = [];
var resizeCanvasOnStart = true;
var progressFunc = null; var progressFunc = null;
var pckProgressTracker = {}; var preloadProgressTracker = {};
var lastProgress = { loaded: 0, total: 0 }; var lastProgress = { loaded: 0, total: 0 };
var canvas = null; var canvas = null;
var executableName = null;
var locale = null;
var stdout = null; var stdout = null;
var stderr = null; var stderr = null;
this.initGame = function(mainPack) { this.init = function(newBasePath) {
if (!gameInitPromise) { if (!initPromise) {
initPromise = Engine.load(newBasePath).then(
if (mainPack === undefined) {
if (basePath !== null) {
mainPack = basePath + '.pck';
} else {
return Promise.reject(new Error("No main pack to load specified"));
}
}
if (basePath === null)
basePath = getBasePath(mainPack);
gameInitPromise = Engine.initEngine().then(
instantiate.bind(this) instantiate.bind(this)
); );
var gameLoadPromise = loadPromise(mainPack, pckProgressTracker).then(function(xhr) { return xhr.response; });
gameInitPromise = Promise.all([gameLoadPromise, gameInitPromise]).then(function(values) {
// resolve with pck
return new Uint8Array(values[0]);
});
if (unloadAfterInit)
gameInitPromise.then(Engine.unloadEngine);
requestAnimationFrame(animateProgress); requestAnimationFrame(animateProgress);
if (unloadAfterInit)
initPromise.then(Engine.unloadEngine);
} }
return gameInitPromise; return initPromise;
}; };
function instantiate(initializer) { function instantiate(wasmBuf) {
var rtenvOpts = { var rtenvProps = {
noInitialRun: true,
thisProgram: getBaseName(basePath),
engine: this, engine: this,
ENV: {},
}; };
if (typeof stdout === 'function') if (typeof stdout === 'function')
rtenvOpts.print = stdout; rtenvProps.print = stdout;
if (typeof stderr === 'function') if (typeof stderr === 'function')
rtenvOpts.printErr = stderr; rtenvProps.printErr = stderr;
if (typeof WebAssembly === 'object' && initializer instanceof ArrayBuffer) { rtenvProps.instantiateWasm = function(imports, onSuccess) {
rtenvOpts.instantiateWasm = function(imports, onSuccess) { WebAssembly.instantiate(wasmBuf, imports).then(function(result) {
WebAssembly.instantiate(initializer, imports).then(function(result) { onSuccess(result.instance);
onSuccess(result.instance); });
}); return {};
return {}; };
};
} else {
throw new Error("Invalid initializer");
}
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
rtenvOpts.onRuntimeInitialized = resolve; rtenvProps.onRuntimeInitialized = resolve;
rtenvOpts.onAbort = reject; rtenvProps.onAbort = reject;
rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts); rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps);
}); });
} }
this.start = function(mainPack) { this.preloadFile = function(pathOrBuffer, bufferFilename) {
return this.initGame(mainPack).then(synchronousStart.bind(this)); if (pathOrBuffer instanceof ArrayBuffer) {
pathOrBuffer = new Uint8Array(pathOrBuffer);
} else if (ArrayBuffer.isView(pathOrBuffer)) {
pathOrBuffer = new Uint8Array(pathOrBuffer.buffer);
}
if (pathOrBuffer instanceof Uint8Array) {
preloadedFiles.push({
name: bufferFilename,
buffer: pathOrBuffer
});
return Promise.resolve();
} else if (typeof pathOrBuffer === 'string') {
return loadPromise(pathOrBuffer, preloadProgressTracker).then(function(xhr) {
preloadedFiles.push({
name: pathOrBuffer,
buffer: xhr.response
});
});
} else {
throw Promise.reject("Invalid object for preloading");
}
}; };
function synchronousStart(pckView) { this.start = function() {
// TODO don't expect canvas when runninng as cli tool
return this.init().then(
Function.prototype.apply.bind(synchronousStart, this, arguments)
);
};
this.startGame = function(mainPack) {
executableName = getBaseName(mainPack);
return Promise.all([this.init(getBasePath(mainPack)), this.preloadFile(mainPack)]).then(
Function.prototype.apply.bind(synchronousStart, this, [])
);
};
function synchronousStart() {
if (canvas instanceof HTMLCanvasElement) { if (canvas instanceof HTMLCanvasElement) {
this.rtenv.canvas = canvas; this.rtenv.canvas = canvas;
} else { } else {
@ -141,15 +160,33 @@
ev.preventDefault(); ev.preventDefault();
}, false); }, false);
this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true); if (locale) {
gameInitPromise = null; this.rtenv.locale = locale;
this.rtenv.callMain(); } else {
this.rtenv.locale = navigator.languages ? navigator.languages[0] : navigator.language;
}
this.rtenv.locale = this.rtenv.locale.split('.')[0];
this.rtenv.resizeCanvasOnStart = resizeCanvasOnStart;
this.rtenv.thisProgram = executableName || getBaseName(basePath);
preloadedFiles.forEach(function(file) {
this.rtenv.FS.createDataFile('/', file.name, new Uint8Array(file.buffer), true, true, true);
}, this);
preloadedFiles = null;
initPromise = null;
this.rtenv.callMain(arguments);
} }
this.setProgressFunc = function(func) { this.setProgressFunc = function(func) {
progressFunc = func; progressFunc = func;
}; };
this.setResizeCanvasOnStart = function(enabled) {
resizeCanvasOnStart = enabled;
};
function animateProgress() { function animateProgress() {
var loaded = 0; var loaded = 0;
@ -157,7 +194,7 @@
var totalIsValid = true; var totalIsValid = true;
var progressIsFinal = true; var progressIsFinal = true;
[loadingFiles, pckProgressTracker].forEach(function(tracker) { [loadingFiles, preloadProgressTracker].forEach(function(tracker) {
Object.keys(tracker).forEach(function(file) { Object.keys(tracker).forEach(function(file) {
if (!tracker[file].final) if (!tracker[file].final)
progressIsFinal = false; progressIsFinal = false;
@ -184,10 +221,20 @@
canvas = elem; canvas = elem;
}; };
this.setExecutableName = function(newName) {
executableName = newName;
};
this.setLocale = function(newLocale) {
locale = newLocale;
};
this.setUnloadAfterInit = function(enabled) { this.setUnloadAfterInit = function(enabled) {
if (enabled && !unloadAfterInit && gameInitPromise) { if (enabled && !unloadAfterInit && initPromise) {
gameInitPromise.then(Engine.unloadEngine); initPromise.then(Engine.unloadEngine);
} }
unloadAfterInit = enabled; unloadAfterInit = enabled;
}; };
@ -222,7 +269,7 @@
Engine.RuntimeEnvironment = engine.RuntimeEnvironment; Engine.RuntimeEnvironment = engine.RuntimeEnvironment;
Engine.initEngine = function(newBasePath) { Engine.load = function(newBasePath) {
if (newBasePath !== undefined) basePath = getBasePath(newBasePath); if (newBasePath !== undefined) basePath = getBasePath(newBasePath);
if (engineLoadPromise === null) { if (engineLoadPromise === null) {
@ -240,7 +287,7 @@
return engineLoadPromise; return engineLoadPromise;
}; };
Engine.unloadEngine = function() { Engine.unload = function() {
engineLoadPromise = null; engineLoadPromise = null;
}; };
@ -259,7 +306,7 @@
if (!file.endsWith('.js')) { if (!file.endsWith('.js')) {
xhr.responseType = 'arraybuffer'; xhr.responseType = 'arraybuffer';
} }
['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) { ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) {
xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker));
}); });
xhr.send(); xhr.send();
@ -274,7 +321,7 @@
this.abort(); this.abort();
return; return;
} else { } else {
loadXHR(resolve, reject, file); setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
} }
} }
@ -301,12 +348,11 @@
break; break;
case 'error': case 'error':
case 'timeout':
if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) {
tracker[file].final = true; tracker[file].final = true;
reject(new Error("Failed loading file '" + file + "'")); reject(new Error("Failed loading file '" + file + "'"));
} else { } else {
loadXHR(resolve, reject, file); setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000);
} }
break; break;

View file

@ -61,7 +61,6 @@ int main(int argc, char *argv[]) {
// run the 'main_after_fs_sync' function // run the 'main_after_fs_sync' function
/* clang-format off */ /* clang-format off */
EM_ASM( EM_ASM(
Module.noExitRuntime = true;
FS.mkdir('/userfs'); FS.mkdir('/userfs');
FS.mount(IDBFS, {}, '/userfs'); FS.mount(IDBFS, {}, '/userfs');
FS.syncfs(true, function(err) { FS.syncfs(true, function(err) {

View file

@ -438,25 +438,23 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i
video_mode = p_desired; video_mode = p_desired;
// can't fulfil fullscreen request due to browser security // can't fulfil fullscreen request due to browser security
video_mode.fullscreen = false; video_mode.fullscreen = false;
set_window_size(Size2(p_desired.width, p_desired.height)); /* clang-format off */
bool resize_canvas_on_start = EM_ASM_INT_V(
return Module.resizeCanvasOnStart;
);
/* clang-format on */
if (resize_canvas_on_start) {
set_window_size(Size2(video_mode.width, video_mode.height));
} else {
Size2 canvas_size = get_window_size();
video_mode.width = canvas_size.width;
video_mode.height = canvas_size.height;
}
// find locale, emscripten only sets "C"
char locale_ptr[16]; char locale_ptr[16];
/* clang-format off */ /* clang-format off */
EM_ASM_({ EM_ASM_ARGS({
var locale = ""; stringToUTF8(Module.locale, $0, 16);
if (Module.locale) {
// best case: server-side script reads Accept-Language early and
// defines the locale to be read here
locale = Module.locale;
} else {
// no luck, use what the JS engine can tell us
// if this turns out not compatible enough, add tests for
// browserLanguage, systemLanguage and userLanguage
locale = navigator.languages ? navigator.languages[0] : navigator.language;
}
locale = locale.split('.')[0];
stringToUTF8(locale, $0, 16);
}, locale_ptr); }, locale_ptr);
/* clang-format on */ /* clang-format on */
setenv("LANG", locale_ptr, true); setenv("LANG", locale_ptr, true);