My favorites | Sign in
Project Home Downloads Wiki Source
Search
for
Metadata  
Understanding custom metadata in actionscript and the [StyleBind] and [XmlLoad] items.
Updated Sep 30, 2011 by daten...@mit.edu

Custom Metadata in Actionscript

In Actionscript 3, programmers use metadata to tell the compiler and the IDE information about variables and functions. For example, giving [Event] metadata for a class tells the IDE that this class fires the specified event. Similarly, marking a variable as [Bindable] tells the compiler to add the binding infrastructure for this variable.

/** A class of utilities for interfacing with the browser.
*/
[Event(name="windowUnload", type="Basics.Events.BrowserEvent")]
public class BrowserUtil extends EventDispatcher
{

Example of Event metadata. The IDE knows that the BrowserUtil class dispatches events of the type windowUnload in the class BrowserEvent.

/** A list of instructions to be displayed to the user in a BriefingPanel
 */
[Bindable]
public var instructionsQueue:ArrayCollectionPlus = new ArrayCollectionPlus();

Example of Bindable metadata. The instructionsQueue variable is Bindable so that (in this case) the user interface can detect when instructions are added or removed from the ArrayCollectionPlus.

A lesser known feature of Actionscript is that the programmer can define custom metadata. When I first started working on this project, this was a feature known to advanced tech bloggers, but the question the Internet had was, "Why would you want to?" Since then one particular answer has come up in a few different flavors ( Persisting variables in a database). Here I'm going to present a couple additional uses: creating styles and xml file loading.

The examples presented here are found in the Basics library and are used extensively in the Vanished games.

Custom Metadata Basics

The first step when defining custom metadata is to tell the compiler to not throw away the new metadata. To do this, you need to add a new compiler argument.

-keep-as3-metadata+=StyleBind

The compiler argument to keep the new "StyleBind" metadata

In Flex/Flash Builder you can find the field for compiler arguments by right clicking your project, selecting Properties, and then opening the Flex Compiler (or Flex Library Compiler) option.

Luckily, we don't have to do this for every project where we want to use the custom metadata. If you define the metadata in a library that is included in other projects, the compiler keeps the custom metadata in the new project. For example, in the Vanished code, I define [StyleBind] in the Basics library project only. However, any project that depends on the Basics library gets [StyleBind] for free.

Now that we have our custom metadata being preserved by the compiler, we need to decide how to use it. One option that I considered was modifying the SDK to use the custom metadata at compile time. After all, with so much of Flex available open source, this should be feasible. However, I opted not to do this because then anyone wanting to use a library with the custom metadata would also need to use the custom SDK. I also didn't want to update the SDK every time a new one came out. Some of the time I was saving with utilizing custom metadata would be lost if I had to make Custom SDK 3.2, Custom SDK 3.3, Custom SDK 3.5, etc.

Instead of handling the custom metadata at compile time, the Vanished libraries use it at runtime using Actionscript's excellent reflection and introspection tools.

// Get the list of accessors on this type (including the subclass) and look for 
// ones with the [StyleBind] metadata type.
var accessorsList:XMLList = describeType(this).accessor;
for(var i:int = 0;i<accessorsList.length();i++)
{
	var acces:XML = accessorsList[i];
	var obj:XMLList = acces.metadata.(@name=="StyleBind");

Excerpt from StyleBindObject class. Here we can look up variables with the [StyleBind] metadata at run time.

The above example is taken from the constructor of StyleBindObject. StyleBindObject is one of three base classes defined to handle the [StyleBind] metadata tag. At construction, the class uses reflection (through describeType()) to get an XML representation of various aspects of the object. By searching through the accessors (which includes variables), we can find all the variables with the [StyleBind] metadata.

Once we know which variables have the metadata, we can do something programmatically about that.

[StyleBind]

Our first use of custom metadata was the [StyleBind] tag. Vanished used CSS to allow our artists (who often didn't know much about programming) a simplified way to modify the appearance of the user interface. It was elegant to separate the artists from the code and still allow them to make changes and see them without involving a programmer. Unfortunately, while the standard Flex UI components come with many styles that can be set, they didn't cover all the things that our artists wanted to change. Especially once we started writing our own UI components, we needed to create styles for the artists to tweak and we needed to do that quickly.

The official way to add styles to UI elements is a bit more work than we wanted to invest. You can read the official way to do this over at livedocs. In the example they provide, here is (most of) the code that needs to be added to a class to define one new style:

[Style(name="fillColors",type="Array",format="Color",inherit="no")]
public class StyledRectangle extends UIComponent
{    
    // Define a static variable.
    private static var classConstructed:Boolean = classConstruct();

    // Define a static method.
    private static function classConstruct():Boolean {
        if (!StyleManager.getStyleDeclaration("StyledRectangle"))
        {
            // If there is no CSS definition for StyledRectangle, 
            // then create one and set the default value.
            var myRectStyles:CSSStyleDeclaration = new CSSStyleDeclaration();
            myRectStyles.defaultFactory = function():void
            {
                this.fillColors = [0xFF0000, 0x0000FF];
            }
            StyleManager.setStyleDeclaration("StyledRectangle", myRectStyles, true);

        }
        return true;
    }
     // Override the styleChanged() method to detect changes in your new style.
    override public function styleChanged(styleProp:String):void {

        super.styleChanged(styleProp);

        // Check to see if style changed. 
        if (styleProp=="fillColors") 
        {
            bStypePropChanged=true; 
            invalidateDisplayList();
            return;
        }
    }
}

Livedocs recommendation on how to add the fillColors style to the StyledRectangle custom class

Note that this (mostly) isn't the code for utilizing the style. It's just the code for making the style exist.

[StyleBind] replaces all of this with one line:

public class StyledRectangle extends StyleBindCanvas
{    
	[StyleBind]
	private var fillColors:Array;
}

How we would write StyledRectangle's fillColors style with [StyleBind]

Here we've created StyledRectangle as a subclass of one of our StyleBind-enabled classes and we've marked fillColors as a [StyleBind] variable. When the constructor runs, fillColors will be looked up in the applied CSS and have the value there assigned to the variable.

For the purposes of making this style effect the appearance of the object, we would probably make the variable [Bindable] as well:

public class StyledRectangle extends StyleBindCanvas
{    
	[StyleBind]
	[Bindable]
	private var fillColors:ArrayCollectionPlus;
}

Practically using [!StyleBind] and [`Bindable] to tie styles to appearance.

By making the variable bindable, we've provided easy access for writing MXML code which will use the variable to modify the appearance.

Now, when an artist wants a CSS-accessible variable added to the code, the programmer needs to only add one short line of code to make the variable available.

In fact, this became easy enough that we started using CSS and [StyleBind] for non-UI game design tasks. You'll find in the Vanished games that there is a gameParams.css style that is also applied to each of the games and that the styles in there are used to define game balance parameters rather than UI parameters. In general, when we had simple, global values that we wanted to be able to tweak quickly, we put the values in gameParams.css. When we had more complex data structures or data that wasn't a constant across all instances of a class, we stayed with xml configuration files.

[XmlLoad]

After our success with [StyleBind], I had a similar thought when it came to reading those XML configuration files at the start of games. Actionscript has some great (and occasionally annoying) tools for working with XML. For my most common XML task of "Here's a class and I want an XML file to fill in all the variables." the Actionscript XML tools did a good job, but it took a lot of code. I had to do a variety of try/catch statements. I had to sometimes deal with cases of zero/one/many children differently. When there was complex structure, I had to deal with the recursive nature of parsing XML into the data files manually. In the end, I found myself doing a lot of copy-and-paste from one XML-handling routine to another and I knew that things were occasionally being lost or not applied properly.

"I bet custom metadata can help me with this problem too," I thought. Sure enough, it made these loads very simple because I was able to extract all that error checking, count handling, and subclass recursing code into one place that I could just call. Then I could use the simple metadata markup in my new classes and my XML utility had all the information it needed to do the right thing.

/** Height of the map grid.
 */
[Bindable]
[XmlLoad]
public var height:int = 1;
/** The strings representing the years at different depths.
 */
[XmlLoad(optional=true,name="depth")]
[Bindable]
public var depths:ArrayCollectionPlus = new ArrayCollectionPlus();
/** The templates for this map.
 */
[Bindable]
[XmlLoad(name="template",cls="Data.Template",optional=true)]
public var templateList:ArrayCollectionPlus = new ArrayCollectionPlus();

Excerpt of [XmlLoad] code from the TimeRover Map class.

<height>10</height>

<depth>3000 AD</depth> <!-- asteroid -->
<depth>2400 AD</depth> <!-- temperature changes harm crops/yields -->
<depth>2175 AD</depth> <!-- Government starts building facility one-->

<!-- Templates -->
<template>
	<name>wall</name>
	<text>A tall wall stands nearby.</text>
</template>
<template>
	<name>library</name>
	<text>The ruins in this area are filled with fragments of plastic, metal, and silicon consistent with electronic media. Most of it is too damaged to recover.</text>
</template>

Example XML for a TimeRover Map.

You can see in these examples that I've been able to define a Map class that loads its values from an XML file. It contains simple values (like height) arrays of values (like depth) and even another class that needs to be expanded out (Data.Template).

Like the [StyleBind] class, reflection is used to get at this metadata. Unlike [StyleBind], we don't require that the classes using XmlLoad derive from a common base class nor execute this code at object creation time. With XmlLoad, we have a utility function (XmlUtil.loadFromXML()) in the Basics library that can be called to 'fill-in' the values for a class from a loaded XML file.

Switching over to [XmlLoad] saved us a lot of debugging time as all of our XML reads became "bullet-proof" by investing in the writing of one utility function. We also wrote, but have not seriously tested yet, a writeToXML() utility method for serializing an [XmlLoad] object's values back to XML.

Final Thoughts

Like the database permanence example, these custom metadata applications utilize reflection to make the transfer of variable values simple (variable<->database, variable<-CSS, variable<->XML). There are undoubtedly more excellent uses for custom metadata and it's a tool that programmers should remember whenever confronted with a "this variable's values are defined over here and this should be simple" problem.

In the long run, I'd be interested to see custom metadata used for non-variable purposes. Maybe on my next project, I'll have to contemplate how I could empower my IDE using custom Eclipse plugins and custom metadata.


Sign in to add a comment
Powered by Google Project Hosting