diff options
author | Leon Krause <lk@leonkrause.com> | 2017-08-13 13:10:04 +0200 |
---|---|---|
committer | Leon Krause <lk@leonkrause.com> | 2017-09-11 20:56:29 +0200 |
commit | 4db801aaeac130a74197ab43e531ce2533414eb9 (patch) | |
tree | 65cb628baaea9b9c680557b41f603bba276306bf /misc | |
parent | b4ad899ef64df6e341a4cbe52a15109cd3d6b2eb (diff) |
HTML5 start-up overhaul
- Implement promise-based JS interface for custom HTML page
integration
- Add download progress callback
- Add progress bar and indeterminate spinner to default HTML page
- Try downloading files multiple times when failing
- Get rid of godotfs.js
- Separate steps for engine initialization, game initialization and game
start
- Allow multiple games on one HTML page
- Substitution placeholders only used in .html file
- Placeholders renamed: $GODOT_BASE => $GODOT_BASENAME,
$GODOT_TMEM -> $GODOT_TOTAL_MEMORY
- Emscripten Module is now Engine.RuntimeEnvironment (no longer a global)
Diffstat (limited to 'misc')
-rw-r--r-- | misc/dist/html/default.html | 386 | ||||
-rw-r--r-- | misc/dist/html_fs/godotfs.js | 151 |
2 files changed, 386 insertions, 151 deletions
diff --git a/misc/dist/html/default.html b/misc/dist/html/default.html new file mode 100644 index 0000000000..9fae34f97e --- /dev/null +++ b/misc/dist/html/default.html @@ -0,0 +1,386 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang=""> +<head> + <meta charset="utf-8" /> + <title></title> + <style type="text/css"> + + body { + margin: 0; + border: 0 none; + padding: 0; + text-align: center; + background-color: #222226; + font-family: 'Noto Sans', Arial, sans-serif; + } + + + /* Godot Engine default theme style + * ================================ */ + + .godot { + color: #e0e0e0; + background-color: #3b3943; + background-image: linear-gradient(to bottom, #403e48, #35333c); + border: 1px solid #45434e; + box-shadow: 0 0 1px 1px #2f2d35; + } + + button.godot { + font-family: 'Droid Sans', Arial, sans-serif; /* override user agent style */ + padding: 1px 5px; + background-color: #37353f; + background-image: linear-gradient(to bottom, #413e49, #3a3842); + border: 1px solid #514f5d; + border-radius: 1px; + box-shadow: 0 0 1px 1px #2a2930; + } + + button.godot:hover { + color: #f0f0f0; + background-color: #44414e; + background-image: linear-gradient(to bottom, #494652, #423f4c); + border: 1px solid #5a5667; + box-shadow: 0 0 1px 1px #26252b; + } + + button.godot:active { + color: #fff; + background-color: #3e3b46; + background-image: linear-gradient(to bottom, #36343d, #413e49); + border: 1px solid #4f4c59; + box-shadow: 0 0 1px 1px #26252b; + } + + button.godot:disabled { + color: rgba(230, 230, 230, 0.2); + background-color: #3d3d3d; + background-image: linear-gradient(to bottom, #434343, #393939); + border: 1px solid #474747; + box-shadow: 0 0 1px 1px #2d2b33; + } + + + /* Canvas / wrapper + * ================ */ + + #container { + display: inline-block; /* scale with canvas */ + vertical-align: top; /* prevent extra height */ + position: relative; /* root for absolutely positioned overlay */ + margin: 0; + border: 0 none; + padding: 0; + background-color: #0c0c0c; + } + + #canvas { + display: block; + margin: 0 auto; + color: white; + } + + #canvas:focus { + outline: none; + } + + + /* Status display + * ============== */ + + #status { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + /* don't consume click events - make children visible explicitly */ + visibility: hidden; + } + + #status-progress { + width: 244px; + height: 7px; + background-color: #38363A; + border: 1px solid #444246; + padding: 1px; + box-shadow: 0 0 2px 1px #1B1C22; + border-radius: 2px; + visibility: visible; + } + + #status-progress-inner { + height: 100%; + width: 0; + box-sizing: border-box; + transition: width 0.5s linear; + background-color: #202020; + border: 1px solid #222223; + box-shadow: 0 0 1px 1px #27282E; + border-radius: 3px; + } + + #status-indeterminate { + visibility: visible; + position: relative; + } + + #status-indeterminate > div { + width: 3px; + height: 0; + border-style: solid; + border-width: 6px 2px 0 2px; + border-color: #2b2b2b transparent transparent transparent; + transform-origin: center 14px; + position: absolute; + } + + #status-indeterminate > div:nth-child(1) { transform: rotate( 22.5deg); } + #status-indeterminate > div:nth-child(2) { transform: rotate( 67.5deg); } + #status-indeterminate > div:nth-child(3) { transform: rotate(112.5deg); } + #status-indeterminate > div:nth-child(4) { transform: rotate(157.5deg); } + #status-indeterminate > div:nth-child(5) { transform: rotate(202.5deg); } + #status-indeterminate > div:nth-child(6) { transform: rotate(247.5deg); } + #status-indeterminate > div:nth-child(7) { transform: rotate(292.5deg); } + #status-indeterminate > div:nth-child(8) { transform: rotate(337.5deg); } + + #status-notice { + margin: 0 100px; + line-height: 1.3; + visibility: visible; + padding: 4px 6px; + visibility: visible; + } + + + /* Debug output + * ============ */ + + #output-panel { + display: none; + max-width: 700px; + font-size: small; + margin: 6px auto 0; + padding: 0 4px 4px; + text-align: left; + line-height: 2.2; + } + + #output-header { + display: flex; + justify-content: space-between; + align-items: center; + } + + #output-container { + padding: 6px; + background-color: #2c2a32; + box-shadow: inset 0 0 1px 1px #232127; + color: #bbb; + } + + #output-scroll { + line-height: 1; + height: 12em; + overflow-y: scroll; + white-space: pre-wrap; + font-size: small; + font-family: "Lucida Console", Monaco, monospace; + } + </style> +$GODOT_HEAD_INCLUDE +</head> +<body> + <div id="container"> + <canvas id="canvas" oncontextmenu="event.preventDefault();" width="640" height="480"> + HTML5 canvas appears to be unsupported in the current browser.<br /> + Please try updating or use a different browser. + </canvas> + <div id="status"> + <div id='status-progress' style='display: none;' oncontextmenu="event.preventDefault();"><div id ='status-progress-inner'></div></div> + <div id='status-indeterminate' style='display: none;' oncontextmenu="event.preventDefault();"> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + <div></div> + </div> + <div id="status-notice" class="godot" style='display: none;'></div> + </div> + </div> + <div id="output-panel" class="godot"> + <div id="output-header"> + Output: + <button id='output-clear' class='godot' type='button' autocomplete='off'>Clear</button> + </div> + <div id="output-container"><div id="output-scroll"></div></div> + </div> + + <script type="text/javascript" src="$GODOT_BASENAME.js"></script> + <script type="text/javascript">//<![CDATA[ + + var game = new Engine; + + (function() { + + const BASENAME = '$GODOT_BASENAME'; + const MEMORY_SIZE = $GODOT_TOTAL_MEMORY; + const DEBUG_ENABLED = $GODOT_DEBUG_ENABLED; + const INDETERMINATE_STATUS_STEP_MS = 100; + + var container = document.getElementById('container'); + var canvas = document.getElementById('canvas'); + var statusProgress = document.getElementById('status-progress'); + var statusProgressInner = document.getElementById('status-progress-inner'); + var statusIndeterminate = document.getElementById('status-indeterminate'); + var statusNotice = document.getElementById('status-notice'); + + var initializing = true; + var statusMode = 'hidden'; + var indeterminiateStatusAnimationId = 0; + + setStatusMode('indeterminate'); + game.setCanvas(canvas); + game.setAsmjsMemorySize(MEMORY_SIZE); + + function setStatusMode(mode) { + + if (statusMode === mode || !initializing) + return; + [statusProgress, statusIndeterminate, statusNotice].forEach(elem => { + elem.style.display = 'none'; + }); + if (indeterminiateStatusAnimationId !== 0) { + cancelAnimationFrame(indeterminiateStatusAnimationId); + indeterminiateStatusAnimationId = 0; + } + switch (mode) { + case 'progress': + statusProgress.style.display = 'block'; + break; + case 'indeterminate': + statusIndeterminate.style.display = 'block'; + indeterminiateStatusAnimationId = requestAnimationFrame(animateStatusIndeterminate); + break; + case 'notice': + statusNotice.style.display = 'block'; + break; + case 'hidden': + break; + default: + throw new Error("Invalid status mode"); + } + statusMode = mode; + } + + function animateStatusIndeterminate(ms) { + var i = Math.floor(ms / INDETERMINATE_STATUS_STEP_MS % 8); + if (statusIndeterminate.children[i].style.borderTopColor == '') { + Array.prototype.slice.call(statusIndeterminate.children).forEach(child => { + child.style.borderTopColor = ''; + }); + statusIndeterminate.children[i].style.borderTopColor = '#dfdfdf'; + } + requestAnimationFrame(animateStatusIndeterminate); + } + + function setStatusNotice(text) { + + while (statusNotice.lastChild) { + statusNotice.removeChild(statusNotice.lastChild); + } + var lines = text.split('\n'); + lines.forEach((line, index) => { + statusNotice.appendChild(document.createTextNode(line)); + statusNotice.appendChild(document.createElement('br')); + }); + }; + + game.setProgressFunc((current, total) => { + + if (total > 0) { + statusProgressInner.style.width = current/total * 100 + '%'; + setStatusMode('progress'); + if (current === total) { + // wait for progress bar animation + setTimeout(() => { + setStatusMode('indeterminate'); + }, 500); + } + } else { + setStatusMode('indeterminate'); + } + }); + + if (DEBUG_ENABLED) { + var outputRoot = document.getElementById("output-panel"); + var outputScroll = document.getElementById("output-scroll"); + var OUTPUT_MSG_COUNT_MAX = 400; + + document.getElementById('output-clear').addEventListener('click', () => { + while (outputScroll.firstChild) { + outputScroll.firstChild.remove(); + } + }); + + 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(); + } + var msg = document.createElement("div"); + if (String.prototype.trim.call(text).startsWith("**ERROR**")) { + msg.style.color = "#d44"; + } else if (String.prototype.trim.call(text).startsWith("**WARNING**")) { + msg.style.color = "#ccc000"; + } else if (String.prototype.trim.call(text).startsWith("**SCRIPT ERROR**")) { + msg.style.color = "#c6d"; + } + msg.textContent = text; + var scrollToBottom = outputScroll.scrollHeight - (outputScroll.clientHeight + outputScroll.scrollTop) < 10; + outputScroll.appendChild(msg); + if (scrollToBottom) { + outputScroll.scrollTop = outputScroll.scrollHeight; + } + }; + + function printError(text) { + print('**ERROR**' + ":", text); + } + + game.setStdoutFunc(text => { + print(text); + console.log(text); + }); + + game.setStderrFunc(text => { + printError(text); + console.warn(text); + }); + } + + game.start(BASENAME + '.pck').then(() => { + setStatusMode('hidden'); + initializing = false; + }, err => { + if (DEBUG_ENABLED) + printError(err.message); + setStatusNotice(err.message); + setStatusMode('notice'); + initializing = false; + }); + })(); + //]]></script> +</body> +</html> diff --git a/misc/dist/html_fs/godotfs.js b/misc/dist/html_fs/godotfs.js deleted file mode 100644 index 676ee689fb..0000000000 --- a/misc/dist/html_fs/godotfs.js +++ /dev/null @@ -1,151 +0,0 @@ - -var Module; -if (typeof Module === 'undefined') Module = eval('(function() { try { return Module || {} } catch(e) { return {} } })()'); -if (!Module.expectedDataFileDownloads) { - Module.expectedDataFileDownloads = 0; - Module.finishedDataFileDownloads = 0; -} -Module.expectedDataFileDownloads++; -(function() { - - const PACK_FILE_NAME = '$GODOT_PACK_NAME'; - const PACK_FILE_SIZE = $GODOT_PACK_SIZE; - function fetchRemotePackage(packageName, callback, errback) { - var xhr = new XMLHttpRequest(); - xhr.open('GET', packageName, true); - xhr.responseType = 'arraybuffer'; - xhr.onprogress = function(event) { - var url = packageName; - if (event.loaded && event.total) { - if (!xhr.addedTotal) { - xhr.addedTotal = true; - if (!Module.dataFileDownloads) Module.dataFileDownloads = {}; - Module.dataFileDownloads[url] = { - loaded: event.loaded, - total: event.total - }; - } else { - Module.dataFileDownloads[url].loaded = event.loaded; - } - var total = 0; - var loaded = 0; - var num = 0; - for (var download in Module.dataFileDownloads) { - var data = Module.dataFileDownloads[download]; - total += data.total; - loaded += data.loaded; - num++; - } - total = Math.ceil(total * Module.expectedDataFileDownloads/num); - if (Module['setStatus']) Module['setStatus']('Downloading data... (' + loaded + '/' + total + ')'); - } else if (!Module.dataFileDownloads) { - if (Module['setStatus']) Module['setStatus']('Downloading data...'); - } - }; - xhr.onload = function(event) { - var packageData = xhr.response; - callback(packageData); - }; - xhr.send(null); - }; - - function handleError(error) { - console.error('package error:', error); - }; - - var fetched = null, fetchedCallback = null; - fetchRemotePackage(PACK_FILE_NAME, function(data) { - if (fetchedCallback) { - fetchedCallback(data); - fetchedCallback = null; - } else { - fetched = data; - } - }, handleError); - - function runWithFS() { - -function assert(check, msg) { - if (!check) throw msg + new Error().stack; -} - - function DataRequest(start, end, crunched, audio) { - this.start = start; - this.end = end; - this.crunched = crunched; - this.audio = audio; - } - DataRequest.prototype = { - requests: {}, - open: function(mode, name) { - this.name = name; - this.requests[name] = this; - Module['addRunDependency']('fp ' + this.name); - }, - send: function() {}, - onload: function() { - var byteArray = this.byteArray.subarray(this.start, this.end); - - this.finish(byteArray); - - }, - finish: function(byteArray) { - var that = this; - Module['FS_createPreloadedFile'](this.name, null, byteArray, true, true, function() { - Module['removeRunDependency']('fp ' + that.name); - }, function() { - if (that.audio) { - Module['removeRunDependency']('fp ' + that.name); // workaround for chromium bug 124926 (still no audio with this, but at least we don't hang) - } else { - Module.printErr('Preloading file ' + that.name + ' failed'); - } - }, false, true); // canOwn this data in the filesystem, it is a slide into the heap that will never change - this.requests[this.name] = null; - }, - }; - new DataRequest(0, PACK_FILE_SIZE, 0, 0).open('GET', '/' + PACK_FILE_NAME); - - var PACKAGE_PATH; - if (typeof window === 'object') { - PACKAGE_PATH = window['encodeURIComponent'](window.location.pathname.toString().substring(0, window.location.pathname.toString().lastIndexOf('/')) + '/'); - } else { - // worker - PACKAGE_PATH = encodeURIComponent(location.pathname.toString().substring(0, location.pathname.toString().lastIndexOf('/')) + '/'); - } - var PACKAGE_NAME = PACK_FILE_NAME; - var REMOTE_PACKAGE_NAME = PACK_FILE_NAME; - var PACKAGE_UUID = 'b39761ce-0348-4959-9b16-302ed8e1592e'; - - function processPackageData(arrayBuffer) { - Module.finishedDataFileDownloads++; - assert(arrayBuffer, 'Loading data file failed.'); - var byteArray = new Uint8Array(arrayBuffer); - var curr; - - // Reuse the bytearray from the XHR as the source for file reads. - DataRequest.prototype.byteArray = byteArray; - DataRequest.prototype.requests['/' + PACK_FILE_NAME].onload(); - Module['removeRunDependency']('datafile_datapack'); - - }; - Module['addRunDependency']('datafile_datapack'); - - if (!Module.preloadResults) Module.preloadResults = {}; - - Module.preloadResults[PACKAGE_NAME] = {fromCache: false}; - if (fetched) { - processPackageData(fetched); - fetched = null; - } else { - fetchedCallback = processPackageData; - } - - } - if (Module['calledRun']) { - runWithFS(); - } else { - if (!Module['preRun']) Module['preRun'] = []; - Module["preRun"].push(runWithFS); // FS is not initialized yet, wait for it - } - -})(); |