Making the Move to HTML5, Part 3

By David Galeano,Duncan Tebbs

In the installment of this three-part series, seasoned console developers, formerly of Criterion, describe the process of moving to HTML5 development, covering a vast array of essentials. Don't miss Part 1 and Part 2.

In this final installment, we continue our description of HTML5 and related interfaces for functionality relevant to games, and look at strategies for dealing with important issues such as resource loading and security. We then give a brief overview of the state of HTML5 for mobile devices and conclude with some of the reasons we feel developers should already be targeting HTML5 for high-end games.

Networking - Web Sockets

The WebSocket API allows a full-duplex permanent communication between the browser and a server. WebSocket connections start as standard HTTP connections and then get upgraded to the new protocol, which should make WebSocket connections reliable across proxies and firewalls. We found the API very simple to use. Setting a couple of callbacks gives you easy access to a full-duplex communication.

WebSocket connections use TCP as the transport mechanism, which makes it reliable: messages are guaranteed to arrive, and to do so in the order they were sent (unless of course the connection breaks).

As with any other TCP connection, we found latency to be an issue if the browser, server or some Internet node in between tries to optimize for bandwidth instead of optimizing for latency. Some clients or servers will employ Nagle's algorithm to maximize the amount of data they send per packet, and this will introduce delays.

The WebSocket API does not allow connecting directly from one browser to another. One must always connect to a WebSocket server.

The long definition process of the specification created an unfortunate situation where different browsers supported different versions of the protocol, and some changed from one version to another. This means that your servers will need to support several versions of the protocol in order to handle all clients that may try to connect.

As part of the online infrastructure for games, Turbulenz provides a multiplayer server for clients to communicate with each other. This is used in the multiplayer version of Score Rush to synchronize up to four players in a single session.

As not all browsers will support WebSockets, there are emulation libraries such as SockJS which implement a similar API using other mechanisms. These other mechanisms generally require specific server-side support, and the latency of the different fallbacks is likely to be higher than when using WebSockets. The Turbulenz plugin provides WebSocket support for browsers that do not natively support the API.

Audio

The Audio Element

The audio element provides support for loading and playing sound files. It's addition to the HTML standard created a good opportunity for sound in games without using a plugin. Unfortunately, the quality of the early implementations made it unsuitable for anything beyond basic use cases. For example, Firefox did not support the loop property which required code to figure out, via events, when a sound finished in order to restart it. Even then, the silence between the end of the playback and the restart was easily noticed.

Some workarounds required two audio objects created from the same source file, one of them paused and ready to start playing as quickly as possible when the other one finished. This ping-pong between the two sounds performed better than restarting a single one, but added complexity to the code and was a waste of resources for games with complex sound scenes. Other implementations had issues when playing several sounds at the same time, with stuttering and general low quality output. The situation now has improved, but there are still many of the older browsers active in the wild.

The lack of support for a standard file format across all browsers makes the new API difficult to use. Internet Explorer only supports MP3 files, Safari only supports Wav and MP3 files, Firefox only supports Wav and Ogg files. Chrome seems the only browser that supports all three of these formats. This means that in practice sound files should be encoded as both MP3 and Ogg formats to cover all browsers.

Web Audio

The Web Audio API provides better support for high quality sound. Loosely based on OpenAL, it provides accurately timed playback of sound sources, 3D sound, filters and complex channel manipulation. It is a much more suitable solution for games than the audio element, but unfortunately at the time of writing only Chrome supports it.

Unlike OpenAL, the API does not yet support relative 3D sound sources, requiring constant update of the source location to follow the listener around.

Turbulenz has designed a 3D sound API closely following the OpenAL specification and we provide implementation of this API for several different backends: Web Audio, audio element (simulated 3D sound by setting the sound volume based on distance to listener) and if the browser does not provide support for either then our plugin directly implements the API.

Threading and Parallelism

Web Workers

The Web Workers API provides support for running scripts in the background, independently of any foreground page script. These background scripts communicate with the foreground one via messages, and the browser will copy the contents of those messages to avoid shared data issues. Therefore sending large amounts of data can have a performance cost. Future browser versions could support a form of zero-copy when using an ArrayBuffer as the message: one script writes to it, the other reads from it.

Workers have some limitations, they cannot access the DOM, they do not have access to the global window object and they do not have a parent window. Apart from that they behave as any other piece of JavaScript running in the browser.

As described by the API specification, browsers expect workers to be long-lived. They have a high start-up performance cost, and a high per-instance memory cost.

To create a Worker a URL must be specified for the location of the script code the Worker will execute, although there are ways to create Workers from inlined code.

If the browser does not support Web Workers then its functionality will have to be emulated.

OpenCL

The WebCL API provides support for OpenCL on the browser. This API will allow offloading of costly calculations from the JavaScript runtime to the GPU. Although JavaScript performance keeps improving daily, some embarrassingly parallel problems are better solved by parallel vector machines, like GPUs. For example, physics simulations or massive particle systems.

Currently only third party browser plugins support this API. None of the browsers support it natively.

Input

Mouse Lock

The Mouse Lock API overcomes another of the traditional limitations of browser gaming, namely that if the mouse cursor leaves the browser window, the game stops receiving mouse movement events (effectively making it impossible to fully implement traditional first-person shooter camera controls -- the player must keep dragging the mouse back to the browser). This API decouples the visual position of the mouse cursor from the movement information from the mouse hardware.

Security concerns will also limit which code can lock the mouse and when, to avoid malign code controlling the mouse without the user's consent.

Unfortunately, this API still has limited support on modern browsers, and when supported, sometimes it only applies to fullscreen mode.

Gamepad

Support for input events from gaming-specific input devices such as gamepads, joysticks, driving wheels, pedals, and accelerometers is provided by the Gamepad API. The interface is similar to that already provided for keyboard and mouse, but still has only very limited support among modern browsers.

The Turbulenz plugin provides support for input events coming from gamepads and joysticks.


Resource Loading

Loading of resources in the browsers requires HTTP requests, and these requests have high overhead. Depending on the browser the cost can be between 300 and 700 bytes per request, and potentially high latency. The latency will increase with the distance to the servers hosting the data, to between tens of milliseconds to hundreds of milliseconds. Browsers do load multiple resources in parallel but they have a fixed limit of how many resources they load at the same time, usually between 4 to 8 resources in parallel. For example, you can request 1000 resources at the same time but only four may be actively downloading in parallel. Data from these resources will be passed to event callbacks serially as it becomes available.

A good way to reduce latency is to generate requests to servers as close as possible to the user. This requires a network of servers distributed across the world that can serve content to users in their region, usually known as a Content Delivery Network (CDN).

Obviously, connection bandwidth will also affect load times. Average download speeds can range from 200 kilobytes per second to 5 megabytes per second depending on the country.

With these limitations there are two critical recommendations:

  1. Download as little as possible for what is being rendered on screen.
  2. Convince the browser to cache as much as possible locally.

#1 requires traditional techniques employed when loading from an slow optical medium:

The most common compression format supported by all browsers is gzip. Your servers will need to respond to the request with special HTTP headers to indicate the compression format and the browser will automatically decompress the data before passing it to game code. Although gzip is a standard format, the size of the compressed files will vary significantly from compressor to compressor. We found that 7-Zip generates the smallest files (this tool supports several compression formats but only gzip will work natively on your browser). Remember that every byte counts, not only because when hosting data in the cloud you will pay for the volume of data stored and transferred, but also loading times will suffer with very slow connections.

An additional form of compression for JavaScript code is minification. JavaScript code can be quite verbose, and even with gzip compression, all the helpful comments, descriptive variable names and white space formatting adds unnecessary bytes to the download. Several minification tools exist that can reduce the size of JavaScript code to around 25 percent of the original, without changing its functionality. These tools rename local variables, remove comments, remove unnecessary white space, etc:

The second of our recommendations (persuade the browser to cache data) requires also playing with the HTTP header that the server returns with the requested data. Basically the server needs to tell the browser for how long the data is valid and when the browser should check again for an updated version. Of course the browser will still do whatever it wants in many cases. It may decide to cache only really small files or to reserve a very small amount of disk space for the cache, constantly purging and updating it, but most browsers will try to honor the "time to live" information. There are two main ways to tell a browser for how long it should cache your data:

Servers can return both sets of headers for the same file, but we recommend using only the latter because of its simplicity.

The expire or max-age information gets stored per resource, so if the values are too aggressive the browser may not ask for a new version of the file for a long time. If you are updating data or code then your changes may not be reflected for a long time. In the case of both functionality updates and bug fixes this can conflict with the need to deploy new versions as soon as possible. To avoid this issue, resources are usually given unique names. In this way, new resources will be requested by an updated name, which will bypass the existing cache and force a reload of the new data. Unique names are generated either from an incremental version number or the hash of the contents.

At Turbulenz our resources are named with the hash of their contents, and references are translated at runtime from their logical names (e.g. mymesh.dae) to the unique physical ones (e.g. <hash>.dae.json). This allows us to tell the browser to cache the data for 10 years, which can improve loading times dramatically when playing the game for a second time. As updated resources get new unique names, we can release updates almost immediately. Obviously the resource that performs the translation from logical to physical is not cached at all because that information is dynamic.

The AppCache API is worth noting at this point. This allows developers to declare in advance the resources that will be required so the browser can download them ahead of time. The developer has control over what gets cached and what doesn't, and can use this interface to create web applications that work offline.


Saving Data

Sooner or later a game must save data, and the developer must decide where to store it: remotely or locally.

Remotely

Remote storage allows data to be retrieved later from another machine or another user.

Storing data on your servers requires either an HTTP POST request or, if using WebSockets, a message to the server. In both cases the actual storage will potentially take a long time to happen depending on the amount of data. We have found upload bandwidth a tenth or less of the download bandwidth so saved data will take 10 times longer to upload than to download. Games will have to cope with that latency.

Turbulenz provides services for remote storing and retrieving of different kinds of data:

Locally

Depending on the APIs available, the total amount of data that a game can store locally can vary from kilobytes to megabytes. These local storage APIs do not guarantee better performance than loading HTTP resources that are already in the cache.

Traditional methods for storing data locally were severely limited. Cookies, for example, only allowed a handful of kilobytes, and some other methods were not persistent between browsing sessions. Thankfully new APIs have appeared that support data in the order of a megabyte:

To support older browsers you can use libraries like jStorage that provide a consistent storage API, implemented for different back-ends depending on browser support.

Security

Vulnerabilities in your site can expose private information to third parties. Attackers do not even need to break into your systems to compromise somebody else's confidential information. For example, if you present unfiltered user generated content on your page (like comments for example) attackers can insert their own JavaScript code as part of that content to be executed locally by whoever looks at it. This can potentially expose private information or even generate credit card payments without user intervention. The list of known vulnerabilities grows every year and we recommend the developers stay aware of at least the most critical ones.

In order to avoid some of the most common vulnerabilities modern browsers will enforce some limits in what JavaScript can do. For example JavaScript code on one window does not have access to data on another window unless it actually opened it, in which case a reference to the new window is available at creation time.

Other limitations are related to what resources can be requested from domains other than the one hosting the current page. For example, if a page from domain-a.com makes a request for resources on domain-b.com, the request will fail unless the servers answering the latter request support Cross-Origin Resource Sharing (CORS). If the browser itself does not support CORS, then JSONP can be used, but it also requires support on the server side. This limitation regarding resources in separate domains will have an effect when using a third party CDN to distribute data across the world.

Cheating

Another kind of security issue relates to protecting your game from cheating. Browsers have embedded debuggers that allow anyone to stop the execution of JavaScript code at any time and analyze and modify both data and code. All browsers also provide extensive logging functionality for every request your code generates, and they record everything sent or received. Similar issues exist for traditional PC games, but the browser makes it much easier for the user to understand what the game code is doing.

Tools like the extension Greasemonkey for Firefox make it trivial for users to customize and modify the functionality or visual properties of a given page. JavaScript obfuscation will only help marginally. Essentially, information provided to your servers by browser requests cannot be trusted because it can be tracked and edited at source by the users. Server side checks are required for everything claimed by code executing on the browser, to try to detect cheating behaviour. Crowdsourcing methods to detect or report cheating may prove useful for your game.

We've implemented the following solutions:

Mobile Devices

The content of these articles applies to tablets and mobile phones as much as desktops. The main differences between mobile and desktop platforms relate mostly to the lack of the more advanced APIs on smaller devices and obviously the reduction in hardware resources the browser can make available. Namely, less memory and lower CPU and GPU power. Internet connections using 3G also have a much higher latency and lower bandwidth than those on a desktop or laptop.

These limitations often mean that smaller and / or simpler resources are required for a game running on mobile devices compared to the same game running on a desktop or laptop.

Input methods on mobile devices also require special support, touch control does not offer the same response as a mouse and a keyboard. The touch events API provides support for multi-touch events in a similar way to the mouse events but, instead of a single mouse cursor moving around, multiple points are tracked as they move across the screen. You could interpret movement on different regions of the screen as different controls: left side of the screen for translation, right side of the screen for rotation, for example.

The differences listed here represent problems for all games on mobile devices, whether or not they target HTML5 or more platform-specific APIs. However, it is often the case that browsers on mobile systems do not progress as fast as on their desktop counterparts. For example, desktop browsers supported WebGL much earlier than mobile browsers.

To compensate for some of the shortcomings of mobile browsers, Turbulenz is developing a system to allow packaging of games developed with the Turbulenz SDK into native apps for mobile platforms. The system uses an embedded JavaScript engine and native implementations for all the required browser APIs that may be missing in the stock browser. In this way, developers can write game code that runs in the browser on desktops and laptops, and as a packaged app on mobile devices. As support for HTML5 in mobile browsers improves, no code changes will be required to run that same game directly from the web, as is possible today with traditional PCs.

Conclusion

Hopefully this short series of articles has given readers new to HTML5 some insight into its potential. As with any platform there are some issues that must be worked around and some tradeoffs to be made, but through the development of the engine and infrastructure for the turbulenz.com game network we have demonstrated that this technology can already deliver a high quality gaming experience direct to a huge audience, with no download or install required by the end user.

With some appropriate fallback strategies, games can already target a large range of browsers, OSs and devices, from desktop to mobile. As more and more OS and application vendors adopt the standards, games written using HTML5 technology will enable high performance and a better user experience without these intermediate solutions.

In this sense, of all modern cross-platform technologies that are viable for games HTML5 is perhaps the most promising. Combined with the connectivity of the web and the appropriate server infrastructure it opens up all kinds of possibilities for creating, promoting and sharing high-quality content in ways that are not possible with traditional platforms.

Don't miss: Making the Move to HTML5 Part 1, Part 2

Return to the full version of this article
Copyright © UBM Tech, All rights reserved