Browse Source

Change HTML5 start-up API

Rename engine.start() to startGame(), new start() takes string arguments
handed directly to main(). Rename Engine.loadEngine() to load().

Add setLocale(), setResizeCanvasOnStart(), setExecutableName() and
preloadFile().
Leon Krause 7 years ago
parent
commit
35adf718cf

+ 13 - 12
misc/dist/html/default.html

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

+ 5 - 6
platform/javascript/detect.py

@@ -102,14 +102,13 @@ def configure(env):
 
     ## Link flags
 
-    env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"'])
-    env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=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', '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
     if 'module_opus_enabled' in env and env['module_opus_enabled']:

+ 105 - 59
platform/javascript/engine.js

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

+ 0 - 1
platform/javascript/javascript_main.cpp

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

+ 14 - 16
platform/javascript/os_javascript.cpp

@@ -438,25 +438,23 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i
 	video_mode = p_desired;
 	// can't fulfil fullscreen request due to browser security
 	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];
 	/* clang-format off */
-	EM_ASM_({
-		var locale = "";
-		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);
+	EM_ASM_ARGS({
+		stringToUTF8(Module.locale, $0, 16);
 	}, locale_ptr);
 	/* clang-format on */
 	setenv("LANG", locale_ptr, true);