Gnash 0.8.7: the AS2 Array

Our next release of Gnash, 0.8.7, is due in February, and it comes with plenty of changes. This is the first of a couple of posts explaining what I've been working on since the last release and where you can expect improvements.

The inflexible design we inherited from GameSWF is gradually improving to the point where Gnash almost has a stable API for some of its central classes. You still can't rely on it staying the same, but the better design that's emerging (and the fact that Gnash has a design at all now) means that any changes should be limited.

Array changes

As usual, most of the improvements are in ActionScript compatibility. Although they shouldn't be immediately obvious, Gnash will be able to play more SWF files more compatibly than before.

One crucial change is the implementation of the ActionScript 2 Array type. For many practical purposes, an Array is just an Object with a "length" value and properties identified by an integer index. Most functions taking an Array will quite happily accept an Object instead, provided it also has this "length" property.

The following code creates a "fake array" with three elements. It is treated just like a normal array argument if you pass it to a function like MovieClip.beginGradientFill, Function.apply, or XML.addRequestHeaders.

var o:Object = {};
o[1] = 0;
o[2] = 1;
o[3] = 2;
o.length = 3;

Conversely, enumerating an Array's properties does not follow the ordering of the indices, but rather (just like any normal Object) reverse creation order. The following traces "2, 0, 1":

var a:Array = [];
a[2] = "";
a[0] = "";
a[1] = "";
for (i in a) {
    trace(i);
}

The implication of this is that Array indices are almost normal properties, the only difference being the special interaction between the length property and positive integer indices:

// An array's length is set on construction:
var a:Array = [ 0, 1, 2 ];
trace(a.length); // 3

// Setting an positive integer Array property changes its length:
a[5] = 4;
trace(a.length); // 6;

// Changing an Array's length can delete properties:
a.length = 4;
trace(a); // 0,1,2,3

So an Array is effectively nothing more than an Object with some special behaviour when certain properties are set. That unfortunately imposes considerable inefficiency on the design:

  1. Lookup of an index means converting the index to a string and doing a lexicographical lookup (string matching). This makes very large arrays extremely inefficient.
  2. Array indices are treated as strings; every new Array property means adding a hash to Gnash's string lookup table.
  3. Whenever a property of a genuine array is set, the property identifier must be checked to see if it is an index or the length property and the corresponding action taken.

Gnash's previous Array implementation was much more efficient, but unfortunately far less compatible. A particular problem was that the old design rejected any attempt to write an ActionScript subclass of the Array type. This has now been fixed, and the new implementation can play many more SWFs, such as Google's Adwords analysis graphs.