design, video games

How trying to make colored squares in HTML5 is consuming all my time

Update (7 hours later):

Okay. I figured it out. I need to use a canvas object.

TL;DR version: I will start producing the JawsJS video series tomorrow, 16 May.

The new code looks like this:

jaws.Sprite.prototype.setColor = function(value) {
this.color = value;
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = this.width;
canvas.height = this.height;
context.fillStyle = this.color;
context.fillRect(0, 0, this.width, this.height);
this.image = canvas;
}

It’s a separate function entirely that also allows for changing colors during runtime. (For example, as a result of object collision — as I’ll talk about in the first video.)  It also makes sure that the resulting canvas is of the necessary size too.

Original post:

It all started with a simple problem. Because many browsers enforce a fierce interpretation of the same-origin policy on the canvas element in HTML5, trying to test the creation and loading of images can often become a nightmare. The browser, detecting that an image might be loaded from a “file://” protocol, assumes that the canvas is being polluted from sources coming from both HTTP and “file://” at the same time.

That’s a security risk and thus the browser shuts it down. It tries to prevent that from happening. Errors are thrown and, if you are trying to playtest a game, it gets shut down too.

The solution then is to just test using a web server. All the resources will be sent using HTTP and everything can be moved later from a testing to a production environment. However, that introduces another problem: not everyone can setup a web server. Especially in a Windows environment, it can require installing extra software, perhaps even opening ports in firewalls, and having administrative access. It’s too many steps to learn to program games.

Because that’s what I have been wanting to do for weeks now. I promised last month that I would have a video series out on JawsJS, the same game library I’ve been using to make games on nearly a weekly basis, and I haven’t been able to do that yet. I initially thought it would be pretty easy to get all the tools together and then create a series covering making basic art assets, programming in JavaScript, and then moving to publishing — and minifying the source — out for others to play.

However, I ran into that image loading problem a couple weeks ago. After thinking through it some, I came up with what I thought was a good solution: just use colored squares!

I wouldn’t need to package image assets in any of my lesson files and anyone reading the posts later could download and play with the code without having to worry about the image issue. It seemed to solve all of the issues and was also a way to decrease the slope of the learning curve as I could cover image loading in a later lesson as I went over creating and colliding objects early on before getting into animation and more advance topics.

So, after many frustrating attempts and a little bit of a back-and-forth with Ippa (the maintainer of JawsJS), we finally came to a consensus of how I could include code that would, when passed a string with a color name or hexadecimal value, generate a Sprite in the place of what would normally be a loaded image. Problem solved!

Except… no, it turns out the underlying issue was far worse that I imagined. The difference between an Image and other image-like objects is very nuanced and, of course, implemented ever so slightly different per rendering engine.

Let’s start with what I wanted the declaration to be like using its object literal notation.

var player = new jaws.Sprite({color: "red", x: 20, y: 20, width: 20, height: 20});

Nice and simple. The player object is a Sprite of the color red, at position 20, 20 and is a square of 20 pixels. Easy to understand and very human-readable.

And here is the internal code which is supposed to do exactly what I wrote in the declaration code. It should generate a colored square and assign it to the this.image property of Sprite that will later be drawn.

if(!this.image && this.color && this.width && this.height) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.fillStyle = this.color;
context.fillRect(0, 0, this.width, this.height);
this.image = context.getImageData(0, 0, this.width, this.height);
}

Here’s the thing: it doesn’t work. Well, it works, but not as I expected to work.

Instead of creating an Image, it assigns this.image what is known as an ImageData object. And Image and ImageData, as you might imagine, are two different things. One is a DOM object and the other is a specially created object that exists solely as the values used in getImageData and putImageData as part of the HTML5 canvas spec. It is produced by the “get” and then drawn by the “put” functions. No other functionality uses it and it’s not a valid type for drawImage (which is used as part of Sprite’s own draw() function).

I had to come up with a different solution then. What if, I thought, I had the canvas generate an Image for me? It has the very useful toDataURL function that will convert the canvas into a base64 encoded string that can be passed to a new Image as its source property.

if(!this.image && this.color && this.width && this.height) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
context.fillStyle = this.color;
context.fillRect(0, 0, this.width, this.height);
this.image = canvas.getDataURL("image/png");
}

Using that code solved the problem… sometimes. It turns out I rediscovered a bug that has been around in Firefox since 2010. (That’s what my tweets were about last night.) In fact, using toDataURL introduces a little-documented bug in all current browsers where, at run-time, if the image has been processed by not loaded by the time the requested renderer calls for it, an error will get thrown. Well, an error will get thrown in Firefox (“NS_ERROR_NOT_AVAILABLE”)  and IE10 (“Unspecified error”). In Chrome, it just fails completely and doesn’t give any feedback about it.

It’s a very strange race condition between the canvas event queue and browser-specific implementation of requestAnimationFrame that only occurs at the boundary between the JaveScript engine and its underlying language. If toDataURL completes first, everything works as it should. If it doesn’t, well, the whole thing will crash. What’s even worse is that I’m not sure how to catch the error within the code. It doesn’t seem to have a fixed label or even level of error. And, as I wrote, Chrome will just fail without doing anything about it.

I also can’t figure out a way to block to code, not that I would want to anyway, to allow for toDataURL to finish before moving on to the rest. The timer associated with the requestAnimationFrame looping is either running or its isn’t. And it’s already been started by the time the the initial setup code has run. By that point, it’s too late. The race condition has already played out and the results will only show up at the first usage of any drawing functionality.

It’s the problem that has consumed all my time for the last several nights. I spent a small part of Sunday night setting up the files and writing out what I wanted to talk about in the first video I was going to produce. Coming back to it Monday night, I began to test all the code to make sure it did what I was going to say it did and discovered this error and race condition. And here I am late Tuesday night and I am no closer to figuring this out.