Creating a new HTML shell and C file
In this section, we are going to create a new shell.c file that exposes several functions called from our JavaScript. We will also use EM_ASM to call the InitWrappers function that we will define inside the new HTML shell file that we will be creating. This function will create wrappers inside JavaScript that can call functions defined in the WebAssembly module. Before creating the new HTML shell file, we need to create the C code that will be called by the JavaScript wrappers inside the HTML shell:
- Create the new shell.c file as follows:
#include <emscripten.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
printf("Hello World\n");
EM_ASM( InitWrappers() );
printf("Initialization Complete\n");
}
void test() {
printf("button test\n");
}
void int_test( int num ) {
printf("int test=%d\n", num);
}
void float_test( float num ) {
printf("float test=%f\n", num);
}
void string_test( char* str ) {
printf("string test=%s\n", str);
}
The main function runs when the WebAssembly module is loaded. At this point, the Module object can use cwrap to create a JavaScript version of that function that we can tie to onclick events on the HTML elements. Inside the main function, the EM_ASM( InitWrappers() ); code calls an InitWrappers() function that is defined inside JavaScript in the HTML shell file. The DOM uses events to call the next four functions.
We will tie a call to the test() function to a button click in the DOM. The int_test function will be passed as a value from an input field in the DOM and will print a message to the console and textarea element that includes that integer, by using a printf statement. The float_test function will be passed a number as a floating point, printed to the console and textarea element. The string_test function will print out a string that is passed in from JavaScript.
Now, we are going to add the following code to an HTML shell file and call it new_shell.html. The code is based on the Emscripten minimal shell file created by the Emscripten team and explained in the previous section. We will present the entire HTML page divided into four parts.
To begin with, there is the beginning of the HTML file and the head element:
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>New Emscripten Shell</title>
<link href="shell.css" rel="stylesheet" type="text/css">
</head>
Next, is the beginning of the body tag. After that, we have several HTML input elements as well as the textarea element:
<body>
<div class="input_box"> </div>
<div class="input_box">
<button id="click_me" class="em_button">Click Me!</button>
</div>
<div class="input_box">
<input type="number" id="int_num" max="9999" min="0" step="1"
value="1" class="em_input">
<button id="int_button" class="em_button">Int Click!</button>
</div>
<div class="input_box">
<input type="number" id="float_num" max="99" min="0"
step="0.01" value="0.0" class="em_input">
<button id="float_button" class="em_button">Float Click!</button>
</div>
<div class="input_box"> </div>
<textarea class="em_textarea" id="output" rows="8"></textarea>
<div id="string_box">
<button id="string_button" class="em_button">String Click!</button>
<input id="string_input">
</div>
After our HTML, we have the beginning of our script tag, and some JavaScript code we have added to the default shell file:
<script type='text/javascript'>
function InitWrappers() {
var test = Module.cwrap('test', 'undefined');
var int_test = Module.cwrap('int_test', 'undefined', ['int']);
var float_test = Module.cwrap('float_test', 'undefined',
['float']);
var string_test = Module.cwrap('string_test', 'undefined',
['string']);
document.getElementById("int_button").onclick = function() {
if( int_test != null ) {
int_test(document.getElementById('int_num').value);
}
}
document.getElementById("string_button").onclick = function() {
if( string_test != null ) {
string_test(document.getElementById('string_input').value);
}
}
document.getElementById("float_button").onclick = function() {
if( float_test != null ) {
float_test(document.getElementById('float_num').value);
}
}
document.getElementById("click_me").onclick = function() {
if( test != null ) {
test();
}
}
}
function runbefore() {
console.log("before module load");
}
function runafter() {
console.log("after module load");
}
Next, we have the Module object that we brought in from the default shell file. After the Module object, we have the end to the script tag, the {{{ SCRIPT }}} tag, which is replaced by Emscripten when compiled, and the ending tags in our file:
var Module = {
preRun: [runbefore],
postRun: [runafter],
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(' ');
/*
// The printf statement in C is currently writing to a
textarea. If we want to write
// to an HTML tag, we would need to run these lines of
codes to make our text HTML safe
text = text.replace(/&/g, "&");
text = text.replace(/</g, "<");
text = text.replace(/>/g, ">");
text = text.replace('\n', '<br>', 'g');
*/
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight;
// focus on bottom
}
};
})(),
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);
}
},
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...');
window.onerror = function() {
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus = function(text) {
if (text) Module.printErr('[post-exception status] ' + text);
};
};
</script>
{{{ SCRIPT }}}
</body>
</html>
These previous four sections all make up a single shell file called new_shell.html. You can create this code by typing out the last four parts into a file you name new_shell.html, or you can download the file from our GitHub page at https://github.com/PacktPublishing/Hands-On-Game-Development-with-WebAssembly/blob/master/Chapter02/new_shell.html.
Now that we have seen the entire new_shell.html file in large chunks, we can spend a little time breaking down the essential parts and going over it at a granular level. You will notice that we removed all of the CSS style code and have created a new shell.css file included with the following line:
<link href="shell.css" rel="stylesheet" type="text/css">
Next, we have reworked the HTML code inside this file to create elements that will interact with the WebAssembly module. First, we are going to add a button that will call the test() function inside the WebAssembly module:
<div class="input_box">
<button id="click_me" class="em_button">Click Me!</button>
</div>
We will style the button and its included div element inside the shell.css file that we have created. We will need to define the function that will be called by the onclick event of this button element inside the JavaScript code we will write later. We will do something similar for the two input/button pairs we will define in the HTML, as demonstrated in the following code block:
<div class="input_box">
<input type="number" id="int_num" max="9999" min="0" step="1"
value="1" class="em_input">
<button id="int_button" class="em_button">Int Click!</button>
</div>
<div class="input_box">
<input type="number" id="float_num" max="99" min="0" step="0.01"
value="0.0" class="em_input">
<button id="float_button" class="em_button">Float Click!</button>
</div>
Like we did with the first button element, we will tie these next two buttons to functions that will make calls into the WebAssembly module. These function calls will also pass the values defined in the input elements into the WebAssembly functions. We have left the textarea element as an output for the printf calls that happen within the WebAssembly module. We have styled it differently in the CSS file, but we will leave the functionality unchanged:
<textarea class="em_textarea" id="output" rows="8"></textarea>
<div id="string_box">
<button id="string_button" class="em_button">String Click!</button>
<input id="string_input">
</div>
Underneath the textarea element, we have added one more button and a string input element. This button will call the string_test function inside the WebAssembly module, passing it the value inside the string_input element as a C char* parameter.
Now that we have defined all of the elements we need in the HTML, we will go through and add some JavaScript code to tie the JavaScript and WebAssembly module together. The first thing we need to do is define the InitWrappers function. InitWrappers will be called from within the main function in the C code:
function InitWrappers() {
var test = Module.cwrap('test', 'undefined');
var int_test = Module.cwrap('int_test', 'undefined', ['int']);
var float_test = Module.cwrap('float_test', 'undefined',
['float']);
var string_test = Module.cwrap('string_test', 'undefined',
['string']);
document.getElementById("int_button").onclick = function() {
if( int_test != null ) {
int_test(document.getElementById('int_num').value);
}
}
document.getElementById("string_button").onclick = function() {
if( string_test != null ) {
string_test(document.getElementById('string_input').value);
}
}
document.getElementById("float_button").onclick = function() {
if( float_test != null ) {
float_test(document.getElementById('float_num').value);
}
}
document.getElementById("click_me").onclick = function() {
if( test != null ) {
test();
}
}
}
This function uses Module.cwrap to create JavaScript function wrappers around the exported functions inside the WebAssembly module. The first parameter we pass to cwrap is the name of the C function we are wrapping. All of these JavaScript functions will return undefined. JavaScript does not have a void type like C, so when we declare the return type in JavaScript, we need to use the undefined type instead. If the function were to return an int or a float, we would need to put the 'number' value here. The final parameter passed into cwrap is an array of strings that represent the C type of the parameters passed into the WebAssembly module.
After we have defined the JavaScript wrappers around the functions, we need to call them from the buttons. The first one of these calls is to the WebAssembly int_test function. Here is how we set the onclick event for the int_button:
document.getElementById("int_button").onclick = function() {
if( int_test != null ) {
int_test(document.getElementById('int_num').value);
}
}
The first thing we will do is check to see whether int_test is defined. If so, we call the int_test wrapper we explained earlier, passing it the value from the int_num input. We then do something similar for all of the other buttons.
The next thing we do is create a runbefore and runafter function that we place in the preRun and postRun arrays on the Module object:
function runbefore() {
console.log("before module load");
}
function runafter() {
console.log("after module load");
}
var Module = {
preRun: [runbefore],
postRun: [runafter],
That will cause "before module load" to be printed to the console before the module is loaded, and "after module load" is printed after the module is loaded. These functions are not required; they are designed to show how you might run code before and after a WebAssembly module is loaded. If you do not want to call the InitWrappers function from the main function in the WebAssembly module, you could instead put that function inside the postRun array.
The remainder of the JavaScript code is similar to what you would find inside the shell_minimal.html file created by Emscripten. We have removed code that is superfluous for this demonstration, such as code related to the spinnerElement, progressElement, and statusElement, as well as code having to do with the HTML5 canvas. It is not that there is anything wrong with leaving that code in JavaScript, but it is not truly necessary for our demonstration, so we have removed it to reduce this shell to the minimum required.