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)
This commit is contained in:
parent
b4ad899ef6
commit
4db801aaea
9 changed files with 778 additions and 543 deletions
386
misc/dist/html/default.html
vendored
Normal file
386
misc/dist/html/default.html
vendored
Normal file
|
@ -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>
|
151
misc/dist/html_fs/godotfs.js
vendored
151
misc/dist/html_fs/godotfs.js
vendored
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
|
@ -20,33 +20,32 @@ for x in javascript_files:
|
||||||
javascript_objects.append(env_javascript.Object(x))
|
javascript_objects.append(env_javascript.Object(x))
|
||||||
|
|
||||||
env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""])
|
env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""])
|
||||||
env.Append(LINKFLAGS=["--shell-file", '"platform/javascript/godot_shell.html"'])
|
|
||||||
|
|
||||||
# output file name without file extension
|
# output file name without file extension
|
||||||
basename = "godot" + env["PROGSUFFIX"]
|
basename = "godot" + env["PROGSUFFIX"]
|
||||||
target_dir = env.Dir("#bin")
|
target_dir = env.Dir("#bin")
|
||||||
js_file = target_dir.File(basename + ".js")
|
|
||||||
implicit_targets = [js_file]
|
|
||||||
|
|
||||||
zip_dir = target_dir.Dir('.javascript_zip')
|
zip_dir = target_dir.Dir('.javascript_zip')
|
||||||
zip_files = env.InstallAs([zip_dir.File("godot.js"), zip_dir.File("godotfs.js")], [js_file, "#misc/dist/html_fs/godotfs.js"])
|
zip_files = env.InstallAs(zip_dir.File('godot.html'), '#misc/dist/html/default.html')
|
||||||
|
|
||||||
|
implicit_targets = []
|
||||||
if env['wasm'] == 'yes':
|
if env['wasm'] == 'yes':
|
||||||
wasm_file = target_dir.File(basename+'.wasm')
|
wasm = target_dir.File(basename + '.wasm')
|
||||||
implicit_targets.append(wasm_file)
|
implicit_targets.append(wasm)
|
||||||
zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm_file))
|
zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm))
|
||||||
|
prejs = env.File('pre_wasm.js')
|
||||||
else:
|
else:
|
||||||
asmjs_files = [target_dir.File(basename+'.asm.js'), target_dir.File(basename+'.html.mem')]
|
asmjs_files = [target_dir.File(basename + '.asm.js'), target_dir.File(basename + '.js.mem')]
|
||||||
zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files))
|
|
||||||
implicit_targets.extend(asmjs_files)
|
implicit_targets.extend(asmjs_files)
|
||||||
|
zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files))
|
||||||
|
prejs = env.File('pre_asmjs.js')
|
||||||
|
|
||||||
# HTML file must be the first target in the list
|
js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0];
|
||||||
html_file = env.Program(["#bin/godot"] + implicit_targets, javascript_objects, PROGSUFFIX=env["PROGSUFFIX"]+".html")[0]
|
zip_files.append(InstallAs(zip_dir.File('godot.js'), js))
|
||||||
Depends(html_file, "godot_shell.html")
|
|
||||||
|
|
||||||
# Emscripten hardcodes file names, so replace common base name with
|
postjs = env.File('engine.js')
|
||||||
# placeholder while leaving extension; also change `.html.mem` to just `.mem`
|
env.Depends(js, [prejs, postjs])
|
||||||
fixup_html = env.Substfile(html_file, SUBST_DICT=[(basename, '$$GODOT_BASE'), ('.html.mem', '.mem')], SUBSTFILESUFFIX='.fixup.html')
|
env.Append(LINKFLAGS=['--pre-js', prejs.path])
|
||||||
|
env.Append(LINKFLAGS=['--post-js', postjs.path])
|
||||||
|
|
||||||
zip_files.append(InstallAs(zip_dir.File('godot.html'), fixup_html))
|
Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET")
|
||||||
Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX']+env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET")
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ 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', 'USE_WEBGL2=1'])
|
||||||
|
|
||||||
if (env['wasm'] == 'yes'):
|
if (env['wasm'] == 'yes'):
|
||||||
|
@ -112,6 +113,7 @@ def configure(env):
|
||||||
else:
|
else:
|
||||||
env.Append(LINKFLAGS=['-s', 'ASM_JS=1'])
|
env.Append(LINKFLAGS=['-s', 'ASM_JS=1'])
|
||||||
env.Append(LINKFLAGS=['--separate-asm'])
|
env.Append(LINKFLAGS=['--separate-asm'])
|
||||||
|
env.Append(LINKFLAGS=['--memory-init-file', '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"] != "no"):
|
if("module_opus_enabled" in env and env["module_opus_enabled"] != "no"):
|
||||||
|
|
366
platform/javascript/engine.js
Normal file
366
platform/javascript/engine.js
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
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;
|
||||||
|
if (typeof WebAssembly === 'object' && initializer instanceof WebAssembly.Module) {
|
||||||
|
rtenvOpts.instantiateWasm = function(imports, onSuccess) {
|
||||||
|
WebAssembly.instantiate(initializer, imports).then(function(result) {
|
||||||
|
onSuccess(result);
|
||||||
|
});
|
||||||
|
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) {
|
||||||
|
return WebAssembly.compile(xhr.response);
|
||||||
|
});
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
|
@ -100,8 +100,8 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
|
||||||
for (int i = 0; i < lines.size(); i++) {
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
|
||||||
String current_line = lines[i];
|
String current_line = lines[i];
|
||||||
current_line = current_line.replace("$GODOT_TMEM", itos(memory_mb * 1024 * 1024));
|
current_line = current_line.replace("$GODOT_TOTAL_MEMORY", itos(memory_mb * 1024 * 1024));
|
||||||
current_line = current_line.replace("$GODOT_BASE", p_name);
|
current_line = current_line.replace("$GODOT_BASENAME", p_name);
|
||||||
current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
|
current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include"));
|
||||||
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
|
current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false");
|
||||||
str_export += current_line + "\n";
|
str_export += current_line + "\n";
|
||||||
|
@ -114,28 +114,6 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditorExportPlatformJavaScript::_fix_fsloader_js(Vector<uint8_t> &p_js, const String &p_pack_name, uint64_t p_pack_size) {
|
|
||||||
|
|
||||||
String str_template = String::utf8(reinterpret_cast<const char *>(p_js.ptr()), p_js.size());
|
|
||||||
String str_export;
|
|
||||||
Vector<String> lines = str_template.split("\n");
|
|
||||||
for (int i = 0; i < lines.size(); i++) {
|
|
||||||
if (lines[i].find("$GODOT_PACK_NAME") != -1) {
|
|
||||||
str_export += lines[i].replace("$GODOT_PACK_NAME", p_pack_name);
|
|
||||||
} else if (lines[i].find("$GODOT_PACK_SIZE") != -1) {
|
|
||||||
str_export += lines[i].replace("$GODOT_PACK_SIZE", itos(p_pack_size));
|
|
||||||
} else {
|
|
||||||
str_export += lines[i] + "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CharString cs = str_export.utf8();
|
|
||||||
p_js.resize(cs.length());
|
|
||||||
for (int i = 0; i < cs.length(); i++) {
|
|
||||||
p_js[i] = cs[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
|
void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) {
|
||||||
|
|
||||||
if (p_preset->get("texture_format/s3tc")) {
|
if (p_preset->get("texture_format/s3tc")) {
|
||||||
|
@ -286,10 +264,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
||||||
|
|
||||||
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug);
|
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug);
|
||||||
file = p_path.get_file();
|
file = p_path.get_file();
|
||||||
} else if (file == "godotfs.js") {
|
|
||||||
|
|
||||||
_fix_fsloader_js(data, pck_path.get_file(), pack_size);
|
|
||||||
file = p_path.get_file().get_basename() + "fs.js";
|
|
||||||
} else if (file == "godot.js") {
|
} else if (file == "godot.js") {
|
||||||
|
|
||||||
file = p_path.get_file().get_basename() + ".js";
|
file = p_path.get_file().get_basename() + ".js";
|
||||||
|
|
|
@ -1,347 +0,0 @@
|
||||||
<!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: 'Droid 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;
|
|
||||||
/* canvas must have border and padding set to zero to
|
|
||||||
* calculate cursor coordinates correctly */
|
|
||||||
border: 0 none;
|
|
||||||
padding: 0;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
#canvas:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Status display
|
|
||||||
* ============== */
|
|
||||||
|
|
||||||
#status-container {
|
|
||||||
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 {
|
|
||||||
line-height: 1.3;
|
|
||||||
cursor: pointer;
|
|
||||||
visibility: visible;
|
|
||||||
padding: 4px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* 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" width="640" height="480" tabindex="0" oncontextmenu="event.preventDefault();">
|
|
||||||
HTML5 canvas appears to be unsupported in the current browser.<br />
|
|
||||||
Please try updating or use a different browser.
|
|
||||||
</canvas>
|
|
||||||
<div id="status-container">
|
|
||||||
<span id="status" class="godot" onclick="this.style.visibility='hidden';">Downloading page...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="output-panel" class="godot">
|
|
||||||
<div id="output-header">
|
|
||||||
Output:
|
|
||||||
<button class="godot" type="button" autocomplete="off" onclick="Presentation.clearOutput();">Clear</button>
|
|
||||||
</div>
|
|
||||||
<div id="output-container"><div id="output-scroll"></div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Scripts -->
|
|
||||||
<script type="text/javascript">//<![CDATA[
|
|
||||||
var Presentation = (function() {
|
|
||||||
var statusElement = document.getElementById("status");
|
|
||||||
var canvasElement = document.getElementById("canvas");
|
|
||||||
|
|
||||||
var presentation = {
|
|
||||||
postRun: [],
|
|
||||||
setStatusVisible: function setStatusVisible(visible) {
|
|
||||||
statusElement.style.visibility = (visible ? "visible" : "hidden");
|
|
||||||
},
|
|
||||||
setStatus: function setStatus(text) {
|
|
||||||
if (text.length === 0) {
|
|
||||||
// emscripten sets empty string as status after "Running..."
|
|
||||||
// per timeout, but another status may have been set by then
|
|
||||||
if (Presentation.setStatus.lastText === "Running...")
|
|
||||||
Presentation.setStatusVisible(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Presentation.setStatus.lastText = text;
|
|
||||||
while (statusElement.lastChild) {
|
|
||||||
statusElement.removeChild(statusElement.lastChild);
|
|
||||||
}
|
|
||||||
var lines = text.split("\n");
|
|
||||||
lines.forEach(function(line, index) {
|
|
||||||
statusElement.appendChild(document.createTextNode(line));
|
|
||||||
statusElement.appendChild(document.createElement("br"));
|
|
||||||
});
|
|
||||||
var closeNote = document.createElement("span");
|
|
||||||
closeNote.style.fontSize = "small";
|
|
||||||
closeNote.textContent = "click to close";
|
|
||||||
statusElement.appendChild(closeNote);
|
|
||||||
Presentation.setStatusVisible(true);
|
|
||||||
},
|
|
||||||
isWebGL2Available: function isWebGL2Available() {
|
|
||||||
var context;
|
|
||||||
try {
|
|
||||||
context = canvasElement.getContext("webgl2") || canvasElement.getContext("experimental-webgl2");
|
|
||||||
} catch (e) {}
|
|
||||||
return !!context;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onerror = function(event) { presentation.setStatus("Failure during start-up\nSee JavaScript console") };
|
|
||||||
|
|
||||||
if ($GODOT_DEBUG_ENABLED) { // debugging enabled
|
|
||||||
var outputRoot = document.getElementById("output-panel");
|
|
||||||
var outputElement = document.getElementById("output-scroll");
|
|
||||||
const maxOutputMessages = 400;
|
|
||||||
|
|
||||||
presentation.setOutputVisible = function setOutputVisible(visible) {
|
|
||||||
outputRoot.style.display = (visible ? "block" : "none");
|
|
||||||
};
|
|
||||||
presentation.clearOutput = function clearOutput() {
|
|
||||||
while (outputElement.firstChild) {
|
|
||||||
outputElement.firstChild.remove();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
presentation.setOutputVisible(true);
|
|
||||||
|
|
||||||
presentation.print = function print(text) {
|
|
||||||
if (arguments.length > 1) {
|
|
||||||
text = Array.prototype.slice.call(arguments).join(" ");
|
|
||||||
}
|
|
||||||
if (text.length <= 0) return;
|
|
||||||
while (outputElement.childElementCount >= maxOutputMessages) {
|
|
||||||
outputElement.firstChild.remove();
|
|
||||||
}
|
|
||||||
var msg = document.createElement("div");
|
|
||||||
if (String.prototype.trim.call(text).startsWith("**ERROR**")
|
|
||||||
|| String.prototype.trim.call(text).startsWith("**EXCEPTION**")) {
|
|
||||||
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 = outputElement.scrollHeight - (outputElement.clientHeight + outputElement.scrollTop) < 10;
|
|
||||||
outputElement.appendChild(msg);
|
|
||||||
if (scrollToBottom) {
|
|
||||||
outputElement.scrollTop = outputElement.scrollHeight;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
presentation.postRun.push(function() {
|
|
||||||
window.onerror = function(event) { presentation.print("**EXCEPTION**:", event) };
|
|
||||||
});
|
|
||||||
|
|
||||||
} else {
|
|
||||||
presentation.postRun.push(function() { window.onerror = null; });
|
|
||||||
}
|
|
||||||
|
|
||||||
return presentation;
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Emscripten interface
|
|
||||||
var Module = (function() {
|
|
||||||
const BASE_NAME = '$GODOT_BASE';
|
|
||||||
var module = {
|
|
||||||
thisProgram: BASE_NAME,
|
|
||||||
wasmBinaryFile: BASE_NAME + '.wasm',
|
|
||||||
TOTAL_MEMORY: $GODOT_TMEM,
|
|
||||||
print: function print(text) {
|
|
||||||
if (arguments.length > 1) {
|
|
||||||
text = Array.prototype.slice.call(arguments).join(" ");
|
|
||||||
}
|
|
||||||
console.log(text);
|
|
||||||
if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") {
|
|
||||||
Presentation.print(text);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
printErr: function printErr(text) {
|
|
||||||
if (arguments.length > 1) {
|
|
||||||
text = Array.prototype.slice.call(arguments).join(" ");
|
|
||||||
}
|
|
||||||
console.error(text);
|
|
||||||
if (typeof Presentation !== "undefined" && typeof Presentation.print === "function") {
|
|
||||||
Presentation.print("**ERROR**:", text)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
canvas: document.getElementById("canvas"),
|
|
||||||
setStatus: function setStatus(text) {
|
|
||||||
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
|
|
||||||
var now = Date.now();
|
|
||||||
if (m) {
|
|
||||||
if (now - Date.now() < 30) // if this is a progress update, skip it if too soon
|
|
||||||
return;
|
|
||||||
text = m[1];
|
|
||||||
}
|
|
||||||
if (typeof Presentation !== "undefined" && typeof Presentation.setStatus == "function") {
|
|
||||||
Presentation.setStatus(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// As a default initial behavior, pop up an alert when WebGL context is lost. To make your
|
|
||||||
// application robust, you may want to override this behavior before shipping!
|
|
||||||
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
|
|
||||||
module.canvas.addEventListener("webglcontextlost", function(e) { alert("WebGL context lost. Plase reload the page."); e.preventDefault(); }, false);
|
|
||||||
|
|
||||||
if (typeof Presentation !== "undefined" && Presentation.postRun instanceof Array) {
|
|
||||||
module.postRun = Presentation.postRun;
|
|
||||||
}
|
|
||||||
|
|
||||||
return module;
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!Presentation.isWebGL2Available()) {
|
|
||||||
Presentation.setStatus("WebGL 2 appears to be unsupported.\nPlease update browser and drivers.");
|
|
||||||
Presentation.preventLoading = true;
|
|
||||||
} else {
|
|
||||||
Presentation.setStatus("Downloading...");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Presentation.preventLoading) {
|
|
||||||
// prevent *fs.js and Emscripten's SCRIPT placeholder from loading any files
|
|
||||||
Presentation._XHR_send = XMLHttpRequest.prototype.send;
|
|
||||||
XMLHttpRequest.prototype.send = function() {};
|
|
||||||
Presentation._Node_appendChild = Node.prototype.appendChild;
|
|
||||||
Node.prototype.appendChild = function(node) {
|
|
||||||
if (!(node instanceof HTMLScriptElement)) {
|
|
||||||
return Presentation._Node_appendChild.call(this, node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//]]></script>
|
|
||||||
<script type="text/javascript" src="$GODOT_BASEfs.js"></script>
|
|
||||||
{{{ SCRIPT }}}
|
|
||||||
<script type="text/javascript">
|
|
||||||
if (Presentation.preventLoading) {
|
|
||||||
XMLHttpRequest.prototype.send = Presentation._XHR_send;
|
|
||||||
Node.prototype.appendChild = Presentation._Node_appendChild;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
3
platform/javascript/pre_asmjs.js
Normal file
3
platform/javascript/pre_asmjs.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
var Engine = {
|
||||||
|
USING_WASM: false,
|
||||||
|
RuntimeEnvironment: function(Module) {
|
3
platform/javascript/pre_wasm.js
Normal file
3
platform/javascript/pre_wasm.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
var Engine = {
|
||||||
|
USING_WASM: true,
|
||||||
|
RuntimeEnvironment: function(Module) {
|
Loading…
Reference in a new issue