iteration test with arrays and vectors

Since FLash 10.x is out for a while now, I’m thinking about updating my code to support typed Vectors instead of Arrays. So I made a small benchmark to get an idea of the difference between the performance of an Array and a Vector.

The benchmark creates an array and vector containing int values (assigning the same random value to a certain index in both the Array and Vector). Then various test functions are called which calculate the sum of all values.

The Array and Vector are iterated in the following manners:

  • A normal for loop starting at index 0 and increasing the index and testing it with the length.
  • A normal for loop like the previous, but caching the length of the array in a local variable.
  • A normal for loop starting at the last index and decrease the index till it reaches 0.
  • A for-each-in statement

I also tried an iteration test with for-in statement, but that took so long it gave script timeout errors.

Next to functions expecting an Array parameter or Vector.parameter I also created functions expecting a * type. This because I have a library with support methods for Array which I would like to convert to be useable with Vectors. Since there is no general Vector type the general * type has to be used.

Below is the results using 10 million int values running it on a core i5 760 (not overclocked):

-1148916945, Vector: addVectorWithFor: 100ms
-1148916945, Vector: addVectorWithForCached: 59ms
-1148916945, Vector: addVectorWithForReverse: 57ms
-1148916945, Vector: addVectorWithForEach: 226ms
-1148916945, Vector: addGeneralWithFor: 676ms
-1148916945, Vector: addGeneralWithForCached: 527ms
-1148916945, Vector: addGeneralWithForReverse: 529ms
-1148916945, Vector: addGeneralWithForEach: 220ms
-1148916945, Array: addArrayWithFor: 535ms
-1148916945, Array: addArrayWithForCached: 479ms
-1148916945, Array: addArrayWithForReverse: 475ms
-1148916945, Array: addArrayWithForEach: 189ms
-1148916945, Array: addGeneralWithFor: 683ms
-1148916945, Array: addGeneralWithForCached: 506ms
-1148916945, Array: addGeneralWithForReverse: 484ms
-1148916945, Array: addGeneralWithForEach: 189ms
-- done

The value shown at the start is the calculated sum, to be sure all test functions are working correctly.

Looking at the results the following conclusions can be made:

  • The fastest way to iterate is using a Vector and normal for loop caching the length or using a reversed for loop.
  • The for-each-in statement is the fastest way to iterate an Array.
  • The for-each-in statement takes about the same time for every test and does not seem to depend on type (Arrays seem to be traversed a bit faster then Vectors).

To run the test yourself click here.

Extra note: as a small test I also created a version with debug information included; within the debug projector the normal for loops took longer to execute while the for-each-in statements didn’t show much difference in execution time.

Below is the source of the document class being used:

package  
{
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.text.TextField;
  import flash.text.TextFormat;
  import flash.utils.getTimer;
 
  /**
   * Perform iteration tests on vector and array of int values.
   *
   * @ultraforce
   * J. Munnik
   * www.ultraforce.com
   * Copyright (c) 2011 by Ultra Force Development
   * 
   * version 1
   * - initial version
   * 
   */
  public class BracketOperatorTest extends Sprite
  {
    ///
    /// CONST
    ///
 
    /**
     * Number of values to fill array and vector with
     */
    public static const COUNT: int = 10000000;
 
    ///
    /// PRIVATE VARS
    ///
 
    /**
     * Text field for output.
     */
    private var m_log: TextField;
 
    /**
     * Contains test data
     */
    private var m_intVector: Vector.;
    private var m_intArray: Array;
 
    /**
     * Current test
     */
    private var m_testIndex:Number;
 
    ///
    /// PUBLIC METHODS
    ///
 
    /**
     * Constructor
     */
    public function BracketOperatorTest()
    {
      super();
      if (this.stage)
        this.onAddedToStage();
      else 
        this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
    }
 
    ///
    /// PRIVATE METHODS
    ///
 
    /**
     * Add textline(s) to log window.
     * 
     * @param	aText: text to log
     */
    private function log(aText: *): void
    {
      this.m_log.appendText(aText.toString());
      trace(aText);
    }
 
    /**
     * Create test data.
     */
    private function initTest(): void
    {
      // build test lists
      this.m_intVector = new Vector.(COUNT, true);
      this.m_intArray = new Array(COUNT);
      // fill with data
      for (var index: int = 0; index < COUNT; index++)
      {
        var value: int = Math.floor(Math.random() * COUNT);
        this.m_intArray[index] = value;
        this.m_intVector[index] = value;
      }
      // reset test index
      this.m_testIndex = 0;
    }
 
    /**
     * Run various tests on an array of int and a vector of int 
     * (same number of elements and same values at every index)
     * 
     * @return true if there is another test, false if there are no more tests
     */
    private function runTest(): Boolean
    {
      // perform tests
      var time: int = getTimer();
      // perform certain test and increase test index for next call
      switch(this.m_testIndex++)
      {
        case 0:
          this.log(this.addVectorWithFor(this.m_intVector));
          this.log(', Vector: addVectorWithFor: ' + (getTimer() - time) + 'ms\n');
          break;
        case 1:
          this.log(this.addVectorWithForCached(this.m_intVector));
          this.log(', Vector: addVectorWithForCached: ' + (getTimer() - time) + 'ms\n');
          break;
        case 2:
          this.log(this.addVectorWithForReverse(this.m_intVector));
          this.log(', Vector: addVectorWithForReverse: ' + (getTimer() - time) + 'ms\n');
          break;
        case 3:
          this.log(this.addVectorWithForEach(this.m_intVector));
          this.log(', Vector: addVectorWithForEach: ' + (getTimer() - time) + 'ms\n');
          break;
        case 4:
          this.log(this.addGeneralWithFor(this.m_intVector));
          this.log(', Vector: addGeneralWithFor: ' + (getTimer() - time) + 'ms\n');
          break;
        case 5:
          this.log(this.addGeneralWithForCached(this.m_intVector));
          this.log(', Vector: addGeneralWithForCached: ' + (getTimer() - time) + 'ms\n');
          break;
        case 6:
          this.log(this.addGeneralWithForReverse(this.m_intVector));
          this.log(', Vector: addGeneralWithForReverse: ' + (getTimer() - time) + 'ms\n');
          break;
        case 7:
          this.log(this.addGeneralWithForEach(this.m_intVector));
          this.log(', Vector: addGeneralWithForEach: ' + (getTimer() - time) + 'ms\n');
          break;
        case 8:
          this.log(this.addArrayWithFor(this.m_intArray));
          this.log(', Array: addArrayWithFor: ' + (getTimer() - time) + 'ms\n');
          break;
        case 9:
          this.log(this.addArrayWithForCached(this.m_intArray));
          this.log(', Array: addArrayWithForCached: ' + (getTimer() - time) + 'ms\n');
          break;
        case 10:
          this.log(this.addArrayWithForReverse(this.m_intArray));
          this.log(', Array: addArrayWithForReverse: ' + (getTimer() - time) + 'ms\n');
          break;
        case 11:
          this.log(this.addArrayWithForEach(this.m_intArray));
          this.log(', Array: addArrayWithForEach: ' + (getTimer() - time) + 'ms\n');
          break;
        case 12:
          this.log(this.addGeneralWithFor(this.m_intArray));
          this.log(', Array: addGeneralWithFor: ' + (getTimer() - time) + 'ms\n');
          break;
        case 13:
          this.log(this.addGeneralWithForCached(this.m_intArray));
          this.log(', Array: addGeneralWithForCached: ' + (getTimer() - time) + 'ms\n');
          break;
        case 14:
          this.log(this.addGeneralWithForReverse(this.m_intArray));
          this.log(', Array: addGeneralWithForReverse: ' + (getTimer() - time) + 'ms\n');
          break;
        case 15:
          this.log(this.addGeneralWithForEach(this.m_intArray));
          this.log(', Array: addGeneralWithForEach: ' + (getTimer() - time) + 'ms\n');
          break;
        default:
          this.log('-- done');
          // no more tests available
          return false;
      }
      // more tests available
      return true;
    }
 
    /**
     * Go trough array starting at index 0 to last value.
     * 
     * @param	anArray: array to go trough
     * 
     * @return sum of all int values
     */
    private function addArrayWithFor(anArray: Array): int
    {
      var result: int = 0;
      for (var index: int = 0; index < anArray.length; index++)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Go trough array starting at index 0 to last value.
     * Cache the length of the list in a local variable.
     * 
     * @param	anArray: array to go trough
     * 
     * @return sum of all int values
     */
    private function addArrayWithForCached(anArray: Array): int
    {
      var result: int = 0;
      var count: int = anArray.length;
      for (var index: int = 0; index < count; index++)       {         result += anArray[index];       }       return result     }          /**      * Go trough array starting at last value and ending at first value (at index 0).      *       * @param	anArray: array to go trough      *       * @return sum of all int values      */     private function addArrayWithForReverse(anArray: Array): int     {       var result: int = 0;       for (var index: int = anArray.length - 1; index >= 0; index--)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Go trough array starting using for each statement.
     * 
     * @param	anArray: array to go trough
     * 
     * @return sum of all int values
     */
    private function addArrayWithForEach(anArray: Array): int
    {
      var result: int = 0;
      for each(var value: int in anArray) result += value;
      return result
    }
 
    /**
     * Go trough a general value starting with the value at 
     * the first index and ending with the value at the last index. 
     * 
     * Assume the list has a length property and use the [] operator to 
     * access the values inside.
     * 
     * @param	anArray: general value to go trough
     * 
     * @return sum of all int values
     */
    private function addGeneralWithFor(anArray: *): int
    {
      var result: int = 0;
      for (var index: int = 0; index < anArray.length; index++)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Go trough a general value starting with the value at 
     * the first index and ending with the value at the last index. 
     * 
     * Assume the list has a length property and use the [] operator to 
     * access the values inside.
     * 
     * Cache the length of the list in a local variable.
     * 
     * @param	anArray: general value to go trough
     * 
     * @return sum of all int values
     */
    private function addGeneralWithForCached(anArray: *): int
    {
      var result: int = 0;
      var count: int = anArray.length;
      for (var index: int = 0; index < count; index++)       {         result += anArray[index];       }       return result     }          /**      * Go trough a general value starting with the value at the last       * index and ending with the value at the first index.       *       * Assume the list has a length property and       * use the [] operator to access the values inside.      *       * @param	anArray: general value to go trough      *       * @return sum of all int values      */     private function addGeneralWithForReverse(anArray: *): int     {       var result: int = 0;       for (var index: int = anArray.length - 1; index >= 0; index--)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Use for each statement on a general value.
     * 
     * @param	anArray: general value to go trough
     * 
     * @return sum of all int values
     */
    private function addGeneralWithForEach(anArray: *): int
    {
      var result: int = 0;
      for each(var value: int in anArray) result += value;
      return result
    }
 
    /**
     * Go trough a int vector starting with the value at the first 
     * index and ending with the value at the last index. 
     * 
     * @param	anArray: int vector to go trough
     * 
     * @return sum of all int values
     */
    private function addVectorWithFor(anArray: Vector.): int
    {
      var result: int = 0;
      for (var index: int = 0; index < anArray.length; index++)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Go trough a int vector starting with the value at the first 
     * index and ending with the value at the last index. 
     * Cache the length of the list in a local variable.
     * 
     * @param	anArray: int vector to go trough
     * 
     * @return sum of all int values
     */
    private function addVectorWithForCached(anArray: Vector.): int
    {
      var result: int = 0;
      var count: int = anArray.length;
      for (var index: int = 0; index < count; index++)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Go trough a int vector starting with the value at the last 
     * index and ending with the value at the first index. 
     * 
     * @param	anArray: int vector to go trough
     * 
     * @return sum of all int values
     */
    private function addVectorWithForReverse(anArray: Vector.): int
    {
      var result: int = 0;
      for (var index: int = anArray.length - 1; index >= 0; index--)
      {
        result += anArray[index];
      }
      return result
    }
 
    /**
     * Go trough a int vector using for each statement.
     * 
     * @param	anArray: int vector to go trough
     * 
     * @return sum of all int values
     */
    private function addVectorWithForEach(anArray: Vector.): int
    {
      var result: int = 0;
      for each(var value: int in anArray) result += value;
      return result
    }
 
    ///
    /// EVENTS
    ///
 
    /**
     * Instance is added to stage; run it.
     * 
     * @param	anEvent: event object (default is null)
     */
    private function onAddedToStage(anEvent: Event = null): void 
    {
      this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
      this.m_log = new TextField();
      this.m_log.width = this.stage.stageWidth;
      this.m_log.height = this.stage.stageHeight;
      this.m_log.multiline = true;
      this.m_log.wordWrap = true;
      this.m_log.setTextFormat(
        this.m_log.defaultTextFormat = new TextFormat('_sans')
      );
      this.addChild(this.m_log);
      this.initTest();
      this.addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
 
    /**
     * Run a test. Stop listening to events if tests have finished.
     * 
     * @param	anEvent: event
     */
    private function onEnterFrame(anEvent: Event): void 
    {
      if (!this.runTest()) 
        this.removeEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
  }
}

Using fonts inside a runtime shared library (RSL) loaded trough actionscript in Flash CS4 and CS5

This post describes how to use fonts which are stored in a runtime shared library (RSL) that is loaded trough actionscript. The post continues from the previous post I made about loading RSL via actionscript.

As a start I created two flash files very similar to the other article:

  1. The first file acts as the RSL file. I added Font definition to the library; using Arial Regular. I checked the Export for runtime sharing, used ArialRegular as class name and entered for the URL the name of an empty SWF.
  2. Into a second file I copied and pasted the Font definition from the first file (which automatically turned on Import for runtime sharing. I extended the timeline to 10 frames, created a new keyframe at frame 5 and placed at that frame a dynamic text field with some text using the font from the library. I also placed stop() at frame 5.
    At frame 2 I added the following code:
    this.stop();
    var loader: Loader = new Loader();
    loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
    loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
    var context: LoaderContext = new LoaderContext();
    context.applicationDomain = ApplicationDomain.currentDomain; 
    loader.load(new URLRequest('test.export.picture.swf'), context);
     
    function onComplete(anEvent: Event): void
    {
    	trace('loaded');
    	play();
    }
     
    function onError(anEvent: IOErrorEvent): void
    {
    	trace('error');
    }

    This code will load the RSL and then continues the movie.

As shown in the other post, the Font definition will be present in the main movies context. However after testing the movie, I did not see any text.
It turns out having the Font definition in the same context is not enough, the definition has also to be registered trough the static method Font.registerFont().

So I changed the code at frame 2 to:

...
function onComplete(anEvent: Event): void
{
	trace('loaded');
	Font.registerFont(ApplicationDomain.currentDomain.getDefinition('ArialRegular') as Class);
	play();
}
...

Now the text was showing up when testing the movie.

Summary
To use Font Definition stored into a RSL, it needs to be registered (after the RSL has been loaded into the same domain) trough the method Font.Font.registerFont().
To obtain the definition use the method ApplicationDomain.currentDomain.getDefinition().

Load and use runtime shared libraries trough actionscript in Flash CS4 and CS5

This post describes how to load a runtime shared library (RSL) via actionscript but still be able to use it as a normal RSL within CS4 and CS5. The idea is that instead of letting the flash player automatically load the RSL (whenever it is accessed for the first time), the RSL is loaded with a Loader object via actionscript. The allows the programmer to pick the correct time to load it. Also the loading progress feedback can be shown to the user.

First I describe the various experiments I did; at the end I give a summary of the method I used.

As a start I created two flash files:

  1. The first file is the RSL (called test.export.picture.fla). I placed a png image inside a MovieClip (to get an asset with a nice size in bytes). Then I checked the Export for runtime sharing in the MovieClip properties, which automatically also checks the export and export in frame 1 checkboxes. As URL I used the swf filename (test.export.picture.swf)
  2. In the second file (called test.import.picture.fla) I extended the main timeline to be 10 frames. At frame 5 I placed a key frame and via copy and paste I placed a copy of the MovieClip from the first fla file. This will automatically set the Import for runtime sharing checkbox and fills the URL with the value that was entered at the first file.

After building both flash files, everything was working as expected. Then I tested the 2nd file with the bandwidth profiler turned on (ctrl+B) and with the Simulate Download (pressing ctrl+Enter a second time while viewing the movie). This showed that at frame 5 the RSL was being loaded.

As second part of the experiment I added some actionscript code to the 2nd flash file. At the 2nd frame I added code that loaded the RSL swf file directly using a Loader object. The scriptcode also stopped the movie and continue playing it after the RSL file was completly loaded:

var loader: Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
loader.load(new URLRequest('test.export.picture.swf'));
 
function onComplete(anEvent: Event): void
{
	trace('loaded');
	play();
}
 
function onError(anEvent: IOErrorEvent): void
{
	trace('error');
}

Again I tested the movie with the bandwidth profiler and simulating the download. As expected at frame 2 the RSL file was loaded. But at frame 5 the same file was loaded again. This is not really what I had hoped for. Although there is probably almost no delay the second time the RSL file is loaded because of (memory) caching, one can never be sure. Also it is not clear if this means the RSL file uses 2x the memory.

Next step I changed the actionscript so that the RSL was loaded into the same context as the main SWF:

var loader: Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onError);
var context: LoaderContext = new LoaderContext();
context.applicationDomain = ApplicationDomain.currentDomain; 
loader.load(new URLRequest('test.export.picture.swf'), context);
...

After testing with the bandwidth profiler, nothing seemed to have changed. However I was pretty sure the MovieClip class defined in the RSL file was now available in the main movies context. So as last test I created an empty swf file (by starting a new as3 project without anything in it) and changed the import settings so the URL was pointing to this empty swf file. I tested the movie and it was working (yay!). At frame 5 the small swf got loaded, but since the MovieClip class definition was already available, the flash player still managed to create the MovieClip instance and show it on the main timeline.

Summary
To load and use RSL files via actionscript perform the following steps:

  1. Create an empty Flash file and compile it into a SWF file. This file will act as a dummy file, used by the player when it has to load a RSL file.
  2. Create a Flash file to be used as RSL and add assets normally. When enabling Export for runtime put the name of the dummy swf file at the URL. It seems the url entered here is only used to fill in the URL when creating the imported version of the asset (trough copy and paste).
  3. Copy and paste assets to other library and use them normally.
  4. Make sure the RSL file is loaded completely before using any of the assets from it. Load it within the context of the main movie using something like:
    var loader: Loader = new Loader();
    var context: LoaderContext = new LoaderContext();
    context.applicationDomain = ApplicationDomain.currentDomain; 
    loader.load(new URLRequest(...), context);
  5. If everything works ok, the asset should be used the flash player needing it to load the RSL itself.

Note: instead of using a dummy swf file, one can also use the filename of the SWF that will be initially loaded. It seems flash will not try to load it again. This is ofcourse only usable if a RSL is not used with multiple projects.

To use the above also with shared fonts, some extra work is required. This I will describe in my next post.

Embedding additional font outlines in a runtime shared library (RSL) with Flash CS4

Note that with CS5 this is no longer an issue; since you can specify exactly which font outline groups to embed with the Font Embedding dialog.

However with CS4 it is a different story.

At the end of this blog post I describe a solution how to include additional font outlines. First I describe the experiments I did, in case readers want to recreated those themselves.

As a test I created two flash files:

  • a flash file with a Font definition in the library with export for runtime sharing checked (based upon Arial – Regular)
  • a flash file with a Font definition in the library with import for runtime sharing checked and referencing the first file. I also added a dynamic text field containing some normal text and text using Cyrillic font (Eastern Europe).

The normal text showed up, but not the text with Cyrillic font. Looking at the generated size report it was also clear the Cyrillic outlines did not get exported.

So as next step I placed a dynamic text field in the main timeline of the first runtime sharing fla using the Font definition in the library. I selected the Cyrillic font via Character Embedding… outlines But nothing changed after creating the swf.

I tried various things, but in all cases the Font definition seemed to override the font embed options of the text field. At the end I found a solution (yay!), which I’ll describe next.

Solution
To embed additional (you can not override the default embedded outlines) for a Font definition in CS4 do the following:

  1. create a new Font definition in the library
  2. place a new text field on the main time-line (or use a movie clip, but in that case the movie clip must be exported as well)
  3. set type of text field to Dynamic Text
  4. as Family select the created font definition (make sure it is the font definition in the library and not the font used by the definition)
  5. click Character Embedding… and select the outlines you want to embed as well (no need to select the default ones)
  6. now for the trick to get it working: after this change the text type to Static Text
  7. build the swf file; the swf file should now also contain the additional font outlines

Flex 4, asdoc and custom tags

Today I decided to update to flex 4. After installing version 4.1.0.16076 I ran asdoc to see if the comments in my action script library still were ok. Things had changed though, as I discovered. Below I describe my experience and solution.

Since I’m sending out my library with various project sources I have included a standard header that includes author, website, copyright, version, etc. information. Since there were no comment tags for those, I used some HTML formatting to get a style similar to the rest of the generated documentation.

However after running asdoc from flex 4, part of this formatting had disappeared. The various <br/> tags I used had been filtered out; asdoc also removed style attributes I used within some <span> tags.

Not being a xslt guru and not really wanting to dive into over 300kb of almost completely undocumented xslt code I looked around the internet for a solution.

First I came across asdoc-enhancements. Originally intended for flex 3, I managed to get it working with flex 4. I looked in the downloaded class-files.xslt and then copied the warning code to the new class-files.xslt. I only had to change customs/warning to prolog/asCustoms/warning.

I added a @warning to both my class comment as well to a method comment. After running asdoc, the generated html got a nice red Warning text line at both comments.

So I decided to try to add another custom tag. But this did not work out completely as expected, although the new tag was shown; the @warning tag no longer produced a red Warning text line.

So I looked at the generated xml files (using the -keep-xml option). In some of the generated xml files the warning tag had disappeared. After some digging around, I found the java source at TopLevelClassesGenerator.java. It turns out the method processCustoms() only stores the last custom tag it comes across; instead of storing all.

So it seems you can have a custom tag, but only one 🙁

However not all was lost, it is still possible to use xml tags as value for the custom tag. These will appear as child elements of the custom tag element in the generated xml files.

So instead of various custom tags I defined a single custom tag (@ultraforce), with various xml tags as value. And then alter the class-files.xslt to transform those into formatted HTML.

Here is how it looks inside an .as file:

  /**
   * ...
   * @ultraforce
   * <author email="josha_at_ultraforce.com">J. Munnik</author>
   * <website>www.ultraforce.com</website>
   * <copyright>Copyright (c) 2008 by Ultra Force Development</copyright>

And this is what I added to the end of <xls:template name="description"> in the class-files.xslt (this is my first try at xslt programming, so don’t expect to much):

<xsl:if test="prolog/asCustoms/ultraforce/author">
  <p><b>Author</b><br/><xsl:value-of select="prolog/asCustoms/ultraforce/author/." />
  <xsl:if test="prolog/asCustoms/ultraforce/author/@email">
    <xsl:variable name="email" select="replace(prolog/asCustoms/ultraforce/author/@email, '_at_', '@')" />
    <xsl:value-of disable-output-escaping="yes" select="concat(' (&lt;a href=&quot;mailto:',$email,'&quot; target=&quot;_blank&quot;&gt;',$email,'&lt;/a&gt;)')"/>
  </xsl:if>
  </p>
</xsl:if>
<xsl:if test="prolog/asCustoms/ultraforce/website">
  <xsl:variable name="website" select="prolog/asCustoms/ultraforce/website/." />
  <p><b>Website</b><br/>
  <xsl:value-of disable-output-escaping="yes" select="concat('&lt;a href=&quot;http://',$website,'&quot; target=&quot;_blank&quot;&gt;',$website,'&lt;/a&gt;')"/>
  </p>
</xsl:if>
<xsl:if test="prolog/asCustoms/ultraforce/copyright">
  <p><b>Copyright</b><br/>
  <xsl:value-of disable-output-escaping="yes" select="replace(prolog/asCustoms/ultraforce/copyright/., '\(c\)', '&amp;copy;')" />
  </p>
</xsl:if>

After asdoc, the extra information is formatted correct again. And I have to admit this new way of specifying information is at least more readable in the source then the previous one using HTML formatting.

Still, to bad asdoc does not support multiple custom tags or even better yet, defines various new tags. But who knows, maybe in the future things might change…

Note: it seems the @author tag is supported by the java code and results in a separate element in the generated xml files. So one only has to add a transformation to class-files.xslt to make it visible.

Weakly referenced listeners

I never paid a lot of attention to the 5th parameter of addListener, the useWeakReference parameter.

But the other day I decided to play a bit with it. As a reminder of its function I created a small test.

To create it yourself, perform the following steps:
1. Create new AS3 movieclip in Flash IDE
2. Set framerate to 1 fps
3. Copy following code in the first frame:

// create two event listener objects
var bot1: Object = {
  spam: function(anEvent: Event): void 
  {
    trace('bot1: spam');
  }
};
var bot2: Object = {
  spam: function(anEvent: Event): void 
  {
    trace('bot2: spam');
  }
};
// call spam every frame
this.addEventListener(
  Event.ENTER_FRAME, bot1.spam, false, 0, false
);
this.addEventListener(
  Event.ENTER_FRAME, bot2.spam, false, 0, true
);
// frame counter
var frame: int = 1;
// perform different action every frame
function demo(anEvent: Event): void
{
  switch(frame)
  {
    case 1:
    case 3:
      trace('Garbage collecting');
      System.gc();
      break;
    case 2:
      trace('Removing reference');
      bot1 = null;
      bot2 = null;
      break;
    case 4:
      trace('Removing demo');
      this.removeEventListener(Event.ENTER_FRAME, demo);
      break;
  }
  frame++;
}
// call demo every frame
trace('Adding demo');
this.addEventListener(Event.ENTER_FRAME, demo);

Even though the reference to bot1 is cleared, you will still see trace messages with “bot1: spam”; because there is still a reference to the spam listener method inside bot1. So bot1 does not get removed by the garbage collector.

Flash quirks: SimpleButton can be a DisplayObject.parent

While developing a custom cursor class I came across the following quirk.

One of the things the custom cursor class does is checking if mouseChildren of all parent DisplayObjectContainer instances are enabled.

It retrieves the current objects under the mouse via this.stage.getObjectsUnderPoint and recursively processes the parent properties till the stage instance is reached.

The quirk happens when you mouse over a SimpleButton on the stage which might contain Shape and TextField instances. The parent property of those will be set to SimpleButton instance. The problem is that SimpleButton is not a DisplayObjectContainer subclass, but instead is a subclass of InteractiveObject. So there is no mouseChildren property to access.

With my current Flash and Firefox versions, trying to access this property resulted in the Flash player plugin crashing within FireFox.

To prevent this, check first if the parent is indeed a DisplayObjectContainer instance.

Prototype and as3

On twitter someone asked something which included the prototype property. With as1 I used (like all) prototype to extend and create new classes. With as2 class definitions changed completely and since then I stopped using prototype. I never thought about it, until someone mentioned it on twitter.

I became a bit curious about the prototype property and what its function is within as3; so I did some tests.

It seems Flash looks in prototype property when it can not find a method or variable name within the class (and super classes) definition or instance (in case of dynamic classes). If the prototype property does not contain the method or variable name, Flash will look in the prototype property of the super class (and so on).

The prototype is the last place looked in; so any definition in the class or super class will always come first. Flash also searches first within methods and variables added to a dynamic class instance during run-time.

The only useful thing I could think of so far is the ability to extend existing dynamic classes with methods and variables (not real properties, since there does not seem to be a way to add getter and setter methods). This looked promising, since with as1 I extended several base classes to include additional methods (like the Array, String and Math class). With as2 this was not longer possible and I created static helper classes instead.

Example:

Array.prototype.randomItem = function(): *
{
  return this[Math.floor(Math.random() * this.length)];
};
 
var list: Array = ["Red", "Orange", "Yellow", "Purple"];
 
trace(list.randomItem());
trace(list.randomItem());
trace(list.randomItem());

I was also curious if there would be a difference in calling methods stored in prototype and calling methods of a class/instance. For this i created the following class:

package
{
  public dynamic class One extends Object
  {
    public function One()
    {
      One.prototype.testFunction2 = function()
      {
        var dummy: int = Math.floor(Math.random() * 1000);
      };
 
      One.prototype.testFunction3VeryLongNameAtLeastTwiceAsMuchCharacters = function()
      {
        var dummy: int = Math.floor(Math.random() * 1000);
      };
    }
 
    public function testFunction1()
    {
      var dummy: int = Math.floor(Math.random() * 1000);
    }
  }
}

And then executed the following from a dummy movie within the Flash IDE:

var starttime: int 
var cnt: int;
var one: One = new One();
starttime = getTimer();
for(cnt = 0; cnt < 1000000; cnt++)
{
  one.testFunction1();
}
trace("time: " + (getTimer() - starttime));
starttime = getTimer();
for(cnt = 0; cnt < 1000000; cnt++)
{
  one.testFunction2();
}
trace("time: " + (getTimer() - starttime));
starttime = getTimer();
for(cnt = 0; cnt < 1000000; cnt++)
{
  one.testFunction3VeryLongNameAtLeastTwiceAsMuchCharacters();
}
trace("time: " + (getTimer() - starttime));

As result it turned out that last two loops took almost the double time as the first loop. The last two loops were very similar in duration; so there seems not to be any influence of the length of the function name.

It looks like using prototype has some downsides:

  • Only dynamic classes can be extended, one can not add methods to Math for example.
  • Calls to methods stored in the prototype take longer to execute (see test below)
  • Since methods are added run-time, editors can not show them with code hinting or use the correct syntax highlighting

I’ll probably will never use the prototype property, but maybe someone finds a good use for it.