Hands-On Game Development with WebAssembly
上QQ阅读APP看书,第一时间看更新

The Emscripten minimal shell file

The first build we created with Emscripten used a default HTML shell file. If you have a website, this is probably not the way you would prefer your web page to look. You would probably prefer to design your look and feel using CSS and HTML5 specific to your design or business needs. For instance, the templates I use for my websites typically include advertisements to the left and right of the game's canvas. That is how traffic to these sites is monetized. You may choose to add a logo for your website above your game's canvas. There is also a text area where Emscripten logs output from printf or other standard IO calls. You may choose to remove this textarea element altogether, or you may keep it, but keep it hidden because it is useful for debugging later.

To build the HTML file based on a new shell file that is not the default Emscripten shell, we must use the --shell-file parameter, passing it the new HTML template file we would like to use, instead of Emscripten's default. The new emcc command will look like this:

emcc hello.c --shell-file new_shell.html --emrun -o hello2.html

Do not execute this command just yet. We do not currently have a new_shell.html file in our project directory, so running the command before that file exists will result in an error message. We need to create the new_shell.html file and use it as the HTML shell instead of Emscripten's default HTML shell. This shell file must follow a specific format. To construct it, we have to start with Emscripten's minimum HTML shell file, which you can find at GitHub here:

https://github.com/emscripten-core/emscripten/blob/master/src/shell_minimal.html

We will be writing our own HTML shell, using the shell_minimal.html file as a starting point. Much of what is in the minimal shell is not required, so we will make some significant edits to it. We will remove much of the code to suit our purpose. When you open shell_minimal.html in your text editor, you will see that it starts with a standard HTML header and a style tag:

<style>
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto;
display: block; }
textarea.emscripten { font-family: monospace; width: 80%; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords
will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
.spinner {
height: 50px;
width: 50px;
margin: 0px auto;
-webkit-animation: rotation .8s linear infinite;
-moz-animation: rotation .8s linear infinite;
-o-animation: rotation .8s linear infinite;
animation: rotation 0.8s linear infinite;
border-left: 10px solid rgb(0,150,240);
border-right: 10px solid rgb(0,150,240);
border-bottom: 10px solid rgb(0,150,240);
border-top: 10px solid rgb(100,0,200);
border-radius: 100%;
background-color: rgb(200,100,250);
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
</style>
This code is based on the version of shell_minimal.html available at the time of writing. No changes to this file are anticipated. However, WebAssembly is evolving quickly. Unfortunately, we cannot say with complete certainty that this file will remain unchanged by the time you read this. As mentioned earlier, if you run into problems, please feel free to contact me on Twitter ( @battagline ).

We remove this style tag so you can style your code any way you like. It is necessary if you like their spinner loading image and want to keep it, but it is preferable to yank all of this out and replace it with CSS loaded externally from a CSS file with the link tag, as follows:

<link href="shell.css" rel="stylesheet" type="text/css">

Scroll down a little further, and you will see the loading indicators they use. We are going to replace that with our own eventually, but for now, we are testing all of this locally, and our files are all tiny, so we would remove this code as well:

<figure style="overflow:visible;" id="spinner">
<div class="spinner"></div>
<center style="margin-top:0.5em"><strong>emscripten</strong></center>
</figure>
<div class="emscripten" id="status">Downloading...</div>
<div class="emscripten">
<progress value="0" max="100" id="progress" hidden=1></progress>
</div>

After that, there is an HTML5 canvas element and some other tags related to it. We will eventually need to add a canvas element back in, but for now, we will not be using the canvasso that part of the code is not necessary either:

<div class="emscripten">
<input type="checkbox" id="resize">Resize canvas
<input type="checkbox" id="pointerLock" checked>Lock/hide mouse
pointer&nbsp;&nbsp;&nbsp;
<input type="button" value="Fullscreen" onclick=
"Module.requestFullscreen(document.getElementById
('pointerLock').checked,
document.getElementById('resize').checked)">
</div>

After the canvas, there is a textarea element. That is also not necessary, but it would be good to use it as the location where any printf commands executed from my C code are printed. The shell has surrounded it with two <hr/> tags, used for formatting, so we can remove those as well:

 <hr/>
<textarea class="emscripten" id="output" rows="8"></textarea>
<hr/>

The next thing we have is our JavaScript. That starts with three variables that represent HTML elements that we removed earlier, so we are going to need to remove all of those JavaScript variables as well:

var statusElement = document.getElementById('status');
var progressElement = document.getElementById('progress');
var spinnerElement = document.getElementById('spinner');

The Module object inside JavaScript is the interface that the Emscripten-generated JavaScript glue code uses to interact with our WebAssembly module. It is the most crucial part of a shell HTML file, and it is essential to understand what it is doing. The Module object begins with two arrays, preRunand postRun. These are arrays of functions that will run before and after the module is loaded, respectively.

var Module = {
preRun: [],
postRun: [],

For demonstration purposes, we could add functions to these arrays like this:

preRun: [function() {console.log("pre run 1")},
function() {console.log("pre run 2")}],
postRun: [function() {console.log("post run 1")},
function() {console.log("post run 2")}],

This would produce the following output from our hello WASM app that we created in Chapter1, Introduction to WebAssembly and Emscripten:

pre run 2
pre run 1
status: Running...
Hello wasm
post run 2
post run 1
Notice that the   preRun  and   postRun  functions run in the reverse order in which they are placed in the array. We could use the   postRun  array to call a function that would initialize our WebAssembly wrappers, but, for demonstration purposes, we will instead call a JavaScript function from within our C   main()  function.

The next two functions inside the Module object are the print and printErr functions. The print function is used to print out the output of the printf calls to both the console and to the textarea that we have named output. You can change this output to print out to any HTML tag, but, if your output is raw HTML, there are several commented-out text replace calls that must run. Here is what the print function looks like:

print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text =
Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on
bottom
}
};
})(),

The printErr function is run by the glue code when an error or warning occurs in either our WebAssembly module or the glue code itself. The output of printErr is only the console, although, in principle, if you wanted to add code that would write to an HTML element, you could do that as well. Here is the printErr code:

printErr: function(text) {
if (arguments.length > 1) text =
Array.prototype.slice.call(arguments).join(' ');
if (0) { // XXX disabled for safety typeof dump == 'function') {
dump(text + '\n'); // fast, straight to the real console
} else {
console.error(text);
}
},

After the print functions, there is a canvas function. This function is set up to alert the user to a lost WebGL context. We do not need that code right now, because we have removed the HTML Canvas. When we add the canvas element back in, we will need to restore this function. It also makes sense to update it to handle a lost context event, instead of just alerting the user.

canvas: (function() {
var canvas = document.getElementById('canvas');
// 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
canvas.addEventListener("webglcontextlost", function(e) {
alert('WebGL context lost. You will need to reload the page.');
e.preventDefault(); }, false);
return canvas;
})(),
There are several different situations when your web page could lose its WebGL context. The context is your portal into the GPU, and your app's access to the GPU is managed by both the browser and the operating system. Let's take a trip to The Land of Metaphor, where we   imagine the GPU is a bus, the web browser is the bus driver, and the apps using their context are a bunch of rowdy middle school kids. If the bus driver (browser) feels that the kids (apps) are getting too rowdy, he can stop the bus (GPU), throw all the kids off the bus (make the apps lose their context), and let them come back one at a time if they promise to behave. 

After that, the minimal shell has some code that keeps track of the module's status and dependencies. In this code, we can remove references to the spinnerElement, progressElementand statusElement. Later, if we choose, we can replace these with elements to keep track of the state of loaded modules, but, for the moment, they are not needed. Here is the status and run dependency monitoring code in the minimal shell:

setStatus: function(text) {
if (!Module.setStatus.last) Module.setStatus.last = { time:
Date.now(), text: '' };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();

// if this is a progress update, skip it if too soon
if (m && now - Module.setStatus.last.time < 30) return;
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
}
console.log("status: " + text);
},
totalDependencies: 0,
monitorRunDependencies: function(left) {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-
left) + '/' + this.totalDependencies + ')' : 'All
downloads complete.');
}
};
Module.setStatus('Downloading...');

The final piece of JavaScript code inside the minimal shell file determines what JavaScript will do in the event of a browser error:

window.onerror = function() {
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};

After our JavaScript, there is one more important line:

{{{ SCRIPT }}}

This tag tells Emscripten to place the link to the JavaScript glue code here. Here is an example of what gets compiled into the final HTML file:

<script async type="text/javascript" src="shell-min.js"></script>

shell-min.js is the JavaScript glue code that is built by Emscripten. In the next section, we will learn how to create our own HTML shell file.