| 
									
										
										
										
											2017-08-13 13:10:04 +02:00
										 |  |  | 		return Module; | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | (function() { | 
					
						
							|  |  |  | 	var engine = Engine; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var USING_WASM = engine.USING_WASM; | 
					
						
							|  |  |  | 	var DOWNLOAD_ATTEMPTS_MAX = 4; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var basePath = null; | 
					
						
							|  |  |  | 	var engineLoadPromise = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var loadingFiles = {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function getBasePath(path) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (path.endsWith('/')) | 
					
						
							|  |  |  | 			path = path.slice(0, -1); | 
					
						
							|  |  |  | 		if (path.lastIndexOf('.') > path.lastIndexOf('/')) | 
					
						
							|  |  |  | 			path = path.slice(0, path.lastIndexOf('.')); | 
					
						
							|  |  |  | 		return path; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function getBaseName(path) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		path = getBasePath(path); | 
					
						
							|  |  |  | 		return path.slice(path.lastIndexOf('/') + 1); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Engine = function Engine() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.rtenv = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var gameInitPromise = null; | 
					
						
							|  |  |  | 		var unloadAfterInit = true; | 
					
						
							|  |  |  | 		var memorySize = 268435456; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var progressFunc = null; | 
					
						
							|  |  |  | 		var pckProgressTracker = {}; | 
					
						
							|  |  |  | 		var lastProgress = { loaded: 0, total: 0 }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var canvas = 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); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				gameInitPromise = Engine.initEngine().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); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return gameInitPromise; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		function instantiate(initializer) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var rtenvOpts = { | 
					
						
							|  |  |  | 				noInitialRun: true, | 
					
						
							|  |  |  | 				thisProgram: getBaseName(basePath), | 
					
						
							|  |  |  | 				engine: this, | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			if (typeof stdout === 'function') | 
					
						
							|  |  |  | 				rtenvOpts.print = stdout; | 
					
						
							|  |  |  | 			if (typeof stderr === 'function') | 
					
						
							|  |  |  | 				rtenvOpts.printErr = stderr; | 
					
						
							| 
									
										
										
										
											2017-10-02 13:09:59 +02:00
										 |  |  | 			if (typeof WebAssembly === 'object' && initializer instanceof ArrayBuffer) { | 
					
						
							| 
									
										
										
										
											2017-08-13 13:10:04 +02:00
										 |  |  | 				rtenvOpts.instantiateWasm = function(imports, onSuccess) { | 
					
						
							|  |  |  | 					WebAssembly.instantiate(initializer, imports).then(function(result) { | 
					
						
							| 
									
										
										
										
											2017-10-02 13:09:59 +02:00
										 |  |  | 						onSuccess(result.instance); | 
					
						
							| 
									
										
										
										
											2017-08-13 13:10:04 +02:00
										 |  |  | 					}); | 
					
						
							|  |  |  | 					return {}; | 
					
						
							|  |  |  | 				}; | 
					
						
							|  |  |  | 			} else if (initializer.asm && initializer.mem) { | 
					
						
							|  |  |  | 				rtenvOpts.asm = initializer.asm; | 
					
						
							|  |  |  | 				rtenvOpts.memoryInitializerRequest = initializer.mem; | 
					
						
							|  |  |  | 				rtenvOpts.TOTAL_MEMORY = memorySize; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				throw new Error("Invalid initializer"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return new Promise(function(resolve, reject) { | 
					
						
							|  |  |  | 				rtenvOpts.onRuntimeInitialized = resolve; | 
					
						
							|  |  |  | 				rtenvOpts.onAbort = reject; | 
					
						
							|  |  |  | 				rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.start = function(mainPack) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			return this.initGame(mainPack).then(synchronousStart.bind(this)); | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		function synchronousStart(pckView) { | 
					
						
							|  |  |  | 			// TODO don't expect canvas when runninng as cli tool
 | 
					
						
							|  |  |  | 			if (canvas instanceof HTMLCanvasElement) { | 
					
						
							|  |  |  | 				this.rtenv.canvas = canvas; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var firstCanvas = document.getElementsByTagName('canvas')[0]; | 
					
						
							|  |  |  | 				if (firstCanvas instanceof HTMLCanvasElement) { | 
					
						
							|  |  |  | 					this.rtenv.canvas = firstCanvas; | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					throw new Error("No canvas found"); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var actualCanvas = this.rtenv.canvas; | 
					
						
							|  |  |  | 			var context = false; | 
					
						
							|  |  |  | 			try { | 
					
						
							|  |  |  | 				context = actualCanvas.getContext('webgl2') || actualCanvas.getContext('experimental-webgl2'); | 
					
						
							|  |  |  | 			} catch (e) {} | 
					
						
							|  |  |  | 			if (!context) { | 
					
						
							|  |  |  | 				throw new Error("WebGL 2 not available"); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// canvas can grab focus on click
 | 
					
						
							|  |  |  | 			if (actualCanvas.tabIndex < 0) { | 
					
						
							|  |  |  | 				actualCanvas.tabIndex = 0; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// necessary to calculate cursor coordinates correctly
 | 
					
						
							|  |  |  | 			actualCanvas.style.padding = 0; | 
					
						
							|  |  |  | 			actualCanvas.style.borderWidth = 0; | 
					
						
							|  |  |  | 			actualCanvas.style.borderStyle = 'none'; | 
					
						
							|  |  |  | 			// until context restoration is implemented
 | 
					
						
							|  |  |  | 			actualCanvas.addEventListener('webglcontextlost', function(ev) { | 
					
						
							|  |  |  | 				alert("WebGL context lost, please reload the page"); | 
					
						
							|  |  |  | 				ev.preventDefault(); | 
					
						
							|  |  |  | 			}, false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true); | 
					
						
							|  |  |  | 			gameInitPromise = null; | 
					
						
							|  |  |  | 			this.rtenv.callMain(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setProgressFunc = function(func) { | 
					
						
							|  |  |  | 			progressFunc = func; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		function animateProgress() { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var loaded = 0; | 
					
						
							|  |  |  | 			var total = 0; | 
					
						
							|  |  |  | 			var totalIsValid = true; | 
					
						
							|  |  |  | 			var progressIsFinal = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			[loadingFiles, pckProgressTracker].forEach(function(tracker) { | 
					
						
							|  |  |  | 				Object.keys(tracker).forEach(function(file) { | 
					
						
							|  |  |  | 					if (!tracker[file].final) | 
					
						
							|  |  |  | 						progressIsFinal = false; | 
					
						
							|  |  |  | 					if (!totalIsValid || tracker[file].total === 0) { | 
					
						
							|  |  |  | 						totalIsValid = false; | 
					
						
							|  |  |  | 						total = 0; | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						total += tracker[file].total; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 					loaded += tracker[file].loaded; | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 			if (loaded !== lastProgress.loaded || total !== lastProgress.total) { | 
					
						
							|  |  |  | 				lastProgress.loaded = loaded; | 
					
						
							|  |  |  | 				lastProgress.total = total; | 
					
						
							|  |  |  | 				if (typeof progressFunc === 'function') | 
					
						
							|  |  |  | 					progressFunc(loaded, total); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (!progressIsFinal) | 
					
						
							|  |  |  | 				requestAnimationFrame(animateProgress); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setCanvas = function(elem) { | 
					
						
							|  |  |  | 			canvas = elem; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setAsmjsMemorySize = function(size) { | 
					
						
							|  |  |  | 			memorySize = size; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setUnloadAfterInit = function(enabled) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (enabled && !unloadAfterInit && gameInitPromise) { | 
					
						
							|  |  |  | 				gameInitPromise.then(Engine.unloadEngine); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			unloadAfterInit = enabled; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setStdoutFunc = function(func) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var print = function(text) { | 
					
						
							|  |  |  | 				if (arguments.length > 1) { | 
					
						
							|  |  |  | 					text = Array.prototype.slice.call(arguments).join(" "); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				func(text); | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			if (this.rtenv) | 
					
						
							|  |  |  | 				this.rtenv.print = print; | 
					
						
							|  |  |  | 			stdout = print; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		this.setStderrFunc = function(func) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var printErr = function(text) { | 
					
						
							|  |  |  | 				if (arguments.length > 1) | 
					
						
							|  |  |  | 					text = Array.prototype.slice.call(arguments).join(" "); | 
					
						
							|  |  |  | 				func(text); | 
					
						
							|  |  |  | 			}; | 
					
						
							|  |  |  | 			if (this.rtenv) | 
					
						
							|  |  |  | 				this.rtenv.printErr = printErr; | 
					
						
							|  |  |  | 			stderr = printErr; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	}; // Engine()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Engine.RuntimeEnvironment = engine.RuntimeEnvironment; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Engine.initEngine = function(newBasePath) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (newBasePath !== undefined) basePath = getBasePath(newBasePath); | 
					
						
							|  |  |  | 		if (engineLoadPromise === null) { | 
					
						
							|  |  |  | 			if (USING_WASM) { | 
					
						
							|  |  |  | 				if (typeof WebAssembly !== 'object') | 
					
						
							|  |  |  | 					return Promise.reject(new Error("Browser doesn't support WebAssembly")); | 
					
						
							|  |  |  | 				// TODO cache/retrieve module to/from idb
 | 
					
						
							|  |  |  | 				engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) { | 
					
						
							| 
									
										
										
										
											2017-10-02 13:09:59 +02:00
										 |  |  | 					return xhr.response; | 
					
						
							| 
									
										
										
										
											2017-08-13 13:10:04 +02:00
										 |  |  | 				}); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				var asmjsPromise = loadPromise(basePath + '.asm.js').then(function(xhr) { | 
					
						
							|  |  |  | 					return asmjsModulePromise(xhr.response); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 				var memPromise = loadPromise(basePath + '.mem'); | 
					
						
							|  |  |  | 				engineLoadPromise = Promise.all([asmjsPromise, memPromise]).then(function(values) { | 
					
						
							|  |  |  | 					return { asm: values[0], mem: values[1] }; | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			engineLoadPromise = engineLoadPromise.catch(function(err) { | 
					
						
							|  |  |  | 				engineLoadPromise = null; | 
					
						
							|  |  |  | 				throw err; | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return engineLoadPromise; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function asmjsModulePromise(module) { | 
					
						
							|  |  |  | 		var elem = document.createElement('script'); | 
					
						
							|  |  |  | 		var script = new Blob([ | 
					
						
							|  |  |  | 			'Engine.asm = (function() { var Module = {};', | 
					
						
							|  |  |  | 			module, | 
					
						
							|  |  |  | 			'return Module.asm; })();' | 
					
						
							|  |  |  | 		]); | 
					
						
							|  |  |  | 		var url = URL.createObjectURL(script); | 
					
						
							|  |  |  | 		elem.src = url; | 
					
						
							|  |  |  | 		return new Promise(function(resolve, reject) { | 
					
						
							|  |  |  | 			elem.addEventListener('load', function() { | 
					
						
							|  |  |  | 				URL.revokeObjectURL(url); | 
					
						
							|  |  |  | 				var asm = Engine.asm; | 
					
						
							|  |  |  | 				Engine.asm = undefined; | 
					
						
							|  |  |  | 				setTimeout(function() { | 
					
						
							|  |  |  | 					// delay to reclaim compilation memory
 | 
					
						
							|  |  |  | 					resolve(asm); | 
					
						
							|  |  |  | 				}, 1); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 			elem.addEventListener('error', function() { | 
					
						
							|  |  |  | 				URL.revokeObjectURL(url); | 
					
						
							|  |  |  | 				reject("asm.js faiilure"); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 			document.body.appendChild(elem); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	Engine.unloadEngine = function() { | 
					
						
							|  |  |  | 		engineLoadPromise = null; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function loadPromise(file, tracker) { | 
					
						
							|  |  |  | 		if (tracker === undefined) | 
					
						
							|  |  |  | 			tracker = loadingFiles; | 
					
						
							|  |  |  | 		return new Promise(function(resolve, reject) { | 
					
						
							|  |  |  | 			loadXHR(resolve, reject, file, tracker); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function loadXHR(resolve, reject, file, tracker) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		var xhr = new XMLHttpRequest; | 
					
						
							|  |  |  | 		xhr.open('GET', file); | 
					
						
							|  |  |  | 		if (!file.endsWith('.js')) { | 
					
						
							|  |  |  | 			xhr.responseType = 'arraybuffer'; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) { | 
					
						
							|  |  |  | 			xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); | 
					
						
							|  |  |  | 		}); | 
					
						
							|  |  |  | 		xhr.send(); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	function onXHREvent(resolve, reject, file, tracker, ev) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (this.status >= 400) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (this.status < 500 || ++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { | 
					
						
							|  |  |  | 				reject(new Error("Failed loading file '" + file + "': " + this.statusText)); | 
					
						
							|  |  |  | 				this.abort(); | 
					
						
							|  |  |  | 				return; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				loadXHR(resolve, reject, file); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch (ev.type) { | 
					
						
							|  |  |  | 			case 'loadstart': | 
					
						
							|  |  |  | 				if (tracker[file] === undefined) { | 
					
						
							|  |  |  | 					tracker[file] = { | 
					
						
							|  |  |  | 						total: ev.total, | 
					
						
							|  |  |  | 						loaded: ev.loaded, | 
					
						
							|  |  |  | 						attempts: 0, | 
					
						
							|  |  |  | 						final: false, | 
					
						
							|  |  |  | 					}; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			case 'progress': | 
					
						
							|  |  |  | 				tracker[file].loaded = ev.loaded; | 
					
						
							|  |  |  | 				tracker[file].total = ev.total; | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			case 'load': | 
					
						
							|  |  |  | 				tracker[file].final = true; | 
					
						
							|  |  |  | 				resolve(this); | 
					
						
							|  |  |  | 				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); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			case 'abort': | 
					
						
							|  |  |  | 				tracker[file].final = true; | 
					
						
							|  |  |  | 				reject(new Error("Loading file '" + file + "' was aborted.")); | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | })(); |