Categories Displayed in Flash

Archive for the 'introspection' Category

Flash: Date+ Final + DescribeType + lack of IDate = Bug? Rant!

One of my largest issues with the conversion of AS2 to AS3 is the finalization of types, and the lack of interfaces for basic types. Finalization I can understand from a performance standpoint, the latter is annoying.

Am I missing something? Did Adobe/ECMA think that the core Date class could provide all that might be done?

The case in point is I'm trying to finish porting a Date Extension Utility class from AS2 to AS3. I think it was originally based on this one.   This should normally be straighforward. First snag, Date is now final...grrr. Okay so I'll wrap an inner Date object.   Next issue, Date has lots of methods to implement, and no interface...meaning I have to manually code them, but and there's not way for other classes to treat the new object as a Date object even though it will provide the same features.  Grrr! Which means all the places I used the extention now have to use "*" for type and type checking goes out the window..GRR!

I tried to shortcut and use describeType to help codeGen, but dammit only the accessors and 2 of the myriad of methods show up in the XML regardless if I use the static Date or an instance, the docs don't make it clear which properties are dynamic, which are most of them. Even so I tried for..in which should give some inclination and nothing either apparently it's not enumerable...what's up with that?   Out of curiosity since it's dynamic I tried to see if they could be appended to, can't since it's final.

<pre lang="actionscript">

var d:Date = new Date();

d["oldDate"]     = d.getDate;

d["getDate"] = function(){
trace("newGetDate");
return 3;

}

trace(d.oldDate() + " " + d.getDate());

//outputs

//Console: Warning: 3594: oldDate is not a recognized method of the dynamic class Date.

//Output window:    7 7
</pre>

This is not the first weirdness I've had with introspection, but that was in the early days. Kinda odd that half the introspection relies upon describeType (for the compile time) and the for-in for the dynamic type, instead of a consistent way.  I was hoping that by now they would have fixed it.

Flash: Beware the Garbage Collector

Of all the major changes in AS3, this has been the most problematic....things hanging out off the display list, unreachable. I discovered a new way to get these "voices from the grave" to happen this eve.

We are instantiating new MovieClip classes for the stage via

  1.  
  2.      MovieClipClassReference = getDefinitionByName(styleMC) as Class;
  3.      try{
  4.            curPageClip = new MovieClipClassReference (curPageText);
  5.          }catch (e:Error){
  6.           curPageClip = new MovieClipClassReference ();
  7.       }

The idea being sometimes they accept an argument, in other times they don't. If it doesn't work the first time, then we try the second. Unfortunately this means that there are now 2 MovieClips, with all their assets hanging out in memory, but lacking the brains/script because the constructor errored out! In our case the error was 'voices from beyond' as the sounds embedded on the timeline were playing in the background even though there only appeared to be one instance on the stage stopped at the beginning.

The short solution for us was to have all classes accept a constructor, even if they don't do anything with it. A cleaner approach is to introspect the class looking for the amount of arguments in the constructor...

  1.  
  2.        var desc:XML = describeType(MovieClipClassReference );
  3.         var xmlL : XMLList = desc..constructor.parameter;
  4.         trace("Constructor Parameters " + xmlL.length());
  5.         if( xmlL.length() == 1) {
  6.                 curPageClip = new MovieClipClassReference (curPageText);
  7.         }else if( xmlL.length() == 0) {
  8.                 curPageClip = new MovieClipClassReference ();
  9.         }

Flash: Detecting the end of an animation

Say you have an animated character in a game, that others need to know when they are through playing their animation. Here are a few approaches:

Let's first assume we have a generic object named 'player_mc' that is our animation. We also have a centralized controller script closer to the root, that has a function like:

  1. function onAnimationComplete(evt:Event = null):void{
  2. //do something
  3.  currentPlayer = evt.target as MovieClip;
  4.  currentPlayer.gotoAndStop("still");
  5. }

Note that we use evt = null just in case we want to use call the function without passing it an event.

1) Using addFrameScript

  1. var init:Boolean;
  2. if(!init){
  3.        /// !init check, this block is only needed when pasting on timeline
  4.       // to keep it from activating more than once when the movieClip recycles
  5.         addFrameScript(totalFrames -1, onAnimationComplete);
  6.         init = true;
  7. }
  8.  
  9.  

Just remember if doing that approach, addFrameScript uses a frame number one less that what you see in Flash.

This approach is safer when indexing frame labels prior (e.g. with UIUtil) to map say a frame label of "AnimationFinishedFrame" to the frame number for addFrameScript to use.

  1. addFrameScript(com.troyworks.ui.UIUtil.getNumberOfFrameLabel("AnimationFinishedFrame", onAnimationComplete);
  2.  

2) Using Event Bubbling

Just communicate up the display list.

  1. player.addEventListener("ANIMATION_COMPLETE", onAnimationComplete);

Then

  1. //inside the player on last frame of the player animation
  2.  
  3. stop();
  4. dispatchEvent(new Event("ANIMATION_COMPLETE", true, true));

For more info see here.

3) Use Singleton/Static or Stage as a message bus.

like 2 but using the stage or some other easy to access to communicate without bubbling.

4) Poll for Progress

This is a bit more work, but get's you the ability to find out more about the progress, eg. showing a progress bar for percentage played.

  1. player.addEventListener(Event.ENTER_FRAME, onENTER_FRAME
  2.  
  3. function onENTER_FRAME(evt:Event):void{
  4.   var playermc:MovieClip = evt.target as MovieClip;
  5.  
  6.    if(playermc.currentFrame == playermc.totalFrames){
  7.        onAnimationComplete();
  8.    }
  9. }

Hope that helps.

Flash: View Controller binding

When using actionscript to turn a MovieClip , Sprite etc into a component. There are several points that can be used to introspect the children to bind the displayObject, whatever configuration is on stage, with the ActionScript class. Different binding points allow different things, here's the order and

OUTPUT:

  • constructor: // displayObject children are already on stage in the displayList but haven' t been drawn yet. the Actionscript class for it is just getting instantiated.
  • onFrame1: // actionscript on or associated with frame1 has fired
  • activateHandler: //not always called
  • onFirstFrameRender: //first time ENTER_FRAME has hit the frame1, meaning it's script has fired, and everything is already visible.
  • renderHandler: //only if the stage.invalidate() is called.
  1. package {
  2.  
  3.  import flash.display.MovieClip;
  4.  
  5.     import flash.display.Sprite;
  6.  
  7.     import flash.display.StageAlign;
  8.  
  9.     import flash.display.StageScaleMode;
  10.  
  11.     import flash.events.Event;
  12.  
  13.  import flash.text.TextField;
  14.  
  15. public class StageExample extends MovieClip {
  16.  
  17. public var sizeMe:MovieClip;
  18.  
  19.         public var output_txt:TextField;
  20.  
  21.         public var textLog:Array = new Array();
  22.  
  23. public function StageExample() {
  24.  
  25.                 super();
  26.  
  27.             stage.scaleMode = StageScaleMode.NO_SCALE;
  28.  
  29.             stage.align = StageAlign.TOP_LEFT;
  30.  
  31. stage.addEventListener(Event.ACTIVATE, activateHandler);
  32.  
  33.             stage.addEventListener(Event.RESIZE, resizeHandler);
  34.  
  35. addEventListener(Event.ENTER_FRAME, onFirstFrameRender);
  36.  
  37.                 addEventListener(Event.RENDER, renderHandler);
  38.  
  39. addToLog("constructor: w " + stage.stageWidth + " h " + stage.stageHeight);
  40.  
  41.                 addFrameScript(0, onFrame1);
  42.  
  43.                 stage.invalidate();
  44.  
  45. }
  46.  
  47.         private function onFrame1():void {
  48.  
  49. addToLog("onFrame1: w " + stage.stageWidth + " h " + stage.stageHeight);
  50.  
  51.                 //resizeHandler(event);
  52.  
  53.         }
  54.  
  55.         private function activateHandler(event:Event):void {
  56.  
  57.             trace("onFirstFrameRender: " + event);
  58.  
  59.                 addToLog("activateHandler: w " + stage.stageWidth + " h " + stage.stageHeight);
  60.  
  61.                 //resizeHandler(event);
  62.  
  63.         }
  64.  
  65. private function onFirstFrameRender(event:Event):void {
  66.  
  67.             trace("onFirstFrameRender: " + event);
  68.  
  69.                 addToLog("onFirstFrameRender: w " + stage.stageWidth + " h " + stage.stageHeight);
  70.  
  71.         //      resizeHandler(event);
  72.  
  73.                 removeEventListener(Event.ENTER_FRAME, onFirstFrameRender);
  74.  
  75.         }
  76.  
  77.         private function renderHandler(event:Event):void {
  78.  
  79.         trace("renderHandler: " + event);
  80.  
  81.         addToLog("renderHandler: w " + stage.stageWidth + " h " + stage.stageHeight);
  82.  
  83.     }
  84.  
  85.  ///////////////////////////////////////////////////////////////////
  86.  
  87. private function resizeHandler(event:Event):void {
  88.  
  89.             trace("resizeHandler: " + event);
  90.  
  91.             trace("stageWidth: " + stage.stageWidth + " stageHeight: " + stage.stageHeight);
  92.  
  93. addToLog("resize: w " + stage.stageWidth + " h " + stage.stageHeight);
  94.  
  95.                 sizeMe.width = stage.stageWidth;
  96.  
  97.                 sizeMe.height = stage.stageHeight;
  98.  
  99.         }
  100.  
  101.         private function addToLog(str:String):void
  102.  
  103.         {
  104.  
  105.                 textLog.unshift(str);
  106.  
  107.                 output_txt.text = textLog.join("\r");
  108.  
  109.         }
  110.  
  111.     }
  112.  
  113. }

Flash: 4 Tricks for using MovieClips as Buttons

So here are 4 tricks I've found useful:

  1. Use actionscript to create the Button behavior (enlarging etc), centralizing it in one place.
  2. Use clips styled in the IDE, and copy their style/filters for mouse over, down, disabled state.
  3. Use mouseEnabled (and alpha/brightness) to temporarily disable a button
  4. Use the instance name as the label, and in the event parsing.

(Either JavaScript is not active or you are using an old version of Adobe Flash Player. Please install the newest Flash Player.)

Styling MOUSE_OVER, MOUSE_DOWN States

Similar to CSS centralizing style, centralizing behavioral style offers the same maintenance advantages.   Using clips on the stage for style allows the designer to visually see what's desired, rather than spending countless iterations tweaking actionscript to get it right, it's a good handoff in team based approach.   I also sometimes use describeType 'autowire' any buttons of particular classes.   For the disabled state I stacked two brightness filters ontop of each other.

Styling it to send all the events to a central controller (MVC UCM style), allows things to be debugged far easier than when events are going everywhere, and the controller has internal state, respond appropriately, say ignoring all clicks during loading, without having to rewire all the various components.

For 1 and 2 here's the underlying code. Notice I use mouseChildren = false to keep the label textfield from generating events.

  1.  
  2. function configureMC_Button(ary:Array, addL:Boolean = true):void {var a:MovieClip;
  3.  
  4. var i:int = 0;
  5.  
  6. var n:int =ary.length;
  7.  
  8. for (true; i < n; ++i) {
  9.  
  10. a = ary[i];
  11.  
  12. if (addL) {
  13.  
  14. a.addEventListener(MouseEvent.MOUSE_OVER, onMouseOverHandler);
  15.  
  16. a.addEventListener(MouseEvent.MOUSE_OUT, onMouseOutHandler);
  17.  
  18. a.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
  19.  
  20. a.addEventListener(MouseEvent.MOUSE_UP, onMouseOverHandler);a.addEventListener(MouseEvent.CLICK, onMouseClickHandler);
  21.  
  22. a.mouseChildren =false;
  23.  
  24. } else {
  25.  
  26. a.removeEventListener(MouseEvent.MOUSE_OVER, onMouseOverHandler);
  27.  
  28. a.removeEventListener(MouseEvent.MOUSE_OUT, onMouseOutHandler);
  29.  
  30. a.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
  31.  
  32. a.removeEventListener(MouseEvent.MOUSE_UP, onMouseOverHandler);
  33.  
  34. a.removeEventListener(MouseEvent.CLICK, onMouseClickHandler);
  35.  
  36. }
  37.  
  38. }
  39.  
  40. }
  41.  
  42. function onMouseClickHandler(evt:Event):void {
  43.  
  44. var mc:MovieClip = MovieClip(evt.target);
  45.  
  46. output_txt.text =(" \r"+ mc.label_txt.text + "**Clicked **");
  47.  
  48. }
  49.  
  50. function onMouseDownHandler(evt:Event):void {
  51.  
  52. var mc:MovieClip = MovieClip(evt.target);
  53.  
  54. mc.filters = downTreatment.filters;
  55.  
  56. }
  57.  
  58. function onMouseOverHandler(evt:Event):void {
  59.  
  60. var mc:MovieClip = MovieClip(evt.target);
  61.  
  62. mc.scaleX = mc.scaleY = 1.2;
  63.  
  64. mc.filters = overTreatment.filters;
  65.  
  66. }
  67.  
  68. function onMouseOutHandler(evt:Event):void {
  69.  
  70. var mc:MovieClip = MovieClip(evt.target);
  71.  
  72. mc.scaleX = mc.scaleY = 1;
  73.  
  74. mc.filters = [];
  75.  
  76. }
  77.  
  78. overTreatment.visible = downTreatment.visible = disabledTreatment.visible = false;
  79.  
  80. configureMC_Button([a_btn, b_btn, c_btn, d_btn, e_btn, f_btn, g_btn]);

Enabling and Disabling via ActionScript

You can disable/enable a clip simply by using the mouseEnabled field which will turn on or off mouse events.   Contrast this with the approach of adding/removing listeners to enable or disable a button, which may have multiple listeners for different classes, you can't see, so you never know if you got them all.

  1.  
  2. function setEnabled(  ary:Array, enable:Boolean = true):void {var a:MovieClip;
  3.  
  4. var i:int = 0;
  5.  
  6. var n:int =ary.length;
  7.  
  8. for (true; i < n; ++i) {
  9.  
  10. a = ary[i];
  11.  
  12. if (enable) {
  13.  
  14. a.mouseEnabled = false;
  15.  
  16. a.filters =[];
  17.  
  18. } else {
  19.  
  20. a.mouseEnabled = false;
  21.  
  22. a.filters = disabledTreatment.filters;
  23.  
  24. }
  25.  
  26. }
  27.  
  28. }
  29.  
  30. setEnabled([d_btn, e_btn], false);

Using the instance name as id.

Using the instance as the basis for identification, saying parsing it for use the label, and in the event, can make things more uniform than having buttons dispatching events,.   In internationalized apps where the label changes based on whatever language is present, the instance name can serve as a key to finding the appropriate text in some multilanguage dictionary, or even show when a label is missing.

In Closing
Using a single movieClip has many advantages,  only one skin has to be created instead of 4-5 for a SimpleButton.   When handled in the way outlined above it has many of the advantages of being a component, without the work of being a full blown one.   It minimizes the number of classes/library items when each button is created with a unique label hard coded into it.

In conjunction with a tweening engine, it allows for what I call momentum styled buttons.   Buttons that don't have crisp state changes like SimpleButton attempts on the mouse up/over/down. Things that fade up, and down, like real world lightbulbs, cars.   When used with ColorTransform this can allow for adaptively styled UI, like that of CSS.

There are also safety benefits. In AS3.0 the timeline is purely script driven, there are still bugs when using multiple frames to manage state on particular versions of the flash player.   I've had odd issues with sound not firing or not stopping firing, components blowing up when in tweens or in timelines.

Using ByteArray to Serialize AS3 Classes for AIR, Web and Everywhere!

When using AIR's FileStream to write AS3 classes to ByteArray, I've found that the same file format works for loading in via URLLoader, as well as what's uploaded using FTP. This isn't surprising, but wanted to test anyway.

What this means is that ByteArray is a groovy ubiquitous format for storing full classes and dependencies and references, via ByteArray.writeObject and ByteArray.readObject (provided registerClass is used), without nececessarily having to parse them manually though that is still suggested for versioning and class evolution. Meaning you can create a file using AIR , send that file to a friend in email, and have them open it up in a flash based webpage, and they operating in a webpage could Http POST the change back to you. The ByteArray can have rich complexity, classes, self references, collections, etc. This could be used to save whole game states (even massive multiplayer, NPC brains, etc). Or in my target case debugging outside other non-AIR runtimes to speed development.

How to save to the same directory the swf AIR is running from

This took me a bit to figure out. Primarily as I'm using AIR to write the files in Flash CS3 and it's not immediately apparent in the docs, how to target the 'self' directory of the swf. This isn't recommended in the docs (which I agree) but in the case I'm using it for it makes sense to do so. The API for AIR is here

  1.  
  2.                 // this is in url path
  3.                 var file:File = File.applicationDirectory.resolvePath( "example.jpg" );
  4.                 trace("file " + file.url + " " +file.nativePath ); //outputs app:/example.jpg, and "C:\SomeDirectory\Somepath\example.jpg"
  5.  
  6.                 var wr:File = new File(file.nativePath);
  7.                 // Create a stream and open the file for asynchronous reading
  8.                 var stream:FileStream = new FileStream();
  9.                 stream.open( wr, FileMode.WRITE );
  10.                 stream.write.....
  11.                 stream.close();
  12.  

This would be swf._url in AS2, or in AS3 DisplayObject.loaderInfo.url , as when using relative links in URLLoader to load whatever you're saving this will resolve properly in AIR. Turns out flash.filesystem.File.applicationDirectory is the droid you are looking for. It's not fully qualified (e.g. file:C:\somedirectory...) when traced it looks like:

"app-resource:/yourSwfNameGoesHere.swf"

unless you use the file.nativePath which is required in order to actually write to the directory. So you can use still this in old school string name parsing the swf name out, so you don't have to use File.applicationResourceDirectory if your looking to make a single swf for both AIR and Web use, and on the web, the AIR libraries won't be accessible.

Here's two handy scripts to help inspect ByteArray's, the first looks at the 1's an 0's, the other looks at the objects in the stream, using the getQualifiedClassName, and if it finds an Array or sub ByteArray attempts to inspect it.
/* converts byteArray to binary like

TraceBits--------------------
0 = 110
1 = 1011
2 = 1001000
3 = 1100101
*/

  1.  
  2. function traceBits(bA:ByteArray) {
  3.         trace("TraceBits--------------------");
  4.         for (var i:int = 0; i < bA.length; i++) {
  5.                 bA.position = i;
  6.  
  7.                 var inte:uint = bA.readByte();
  8.                 trace(i+ " = " + inte.toString(2));
  9.         }
  10. }
  11.  
  12. function traceByteArray(bA:ByteArray) {
  13.         var o;
  14.         var cnt:int = 0;
  15.         var cnm:String;
  16.  
  17.         var o2;
  18.         var cnt2:int = 0;
  19.         var cnm2:String;
  20.         try {
  21.                 while (true) {
  22.                         o = bA.readObject();
  23.                         cnm = getQualifiedClassName(o);
  24.                         trace( cnt++ + " " + o + " : " + cnm);
  25.                         if (cnm == "flash.utils::ByteArray") {
  26.                                 try {
  27.                                         cnt2 = 0;
  28.                                         cnm2 = "";
  29.                                         while (true) {
  30.                                                 o2 = o.readObject();
  31.                                                 trace(" " +  cnt2++ + " " + o2 + " : " + getQualifiedClassName(o2));
  32.  
  33.                                         }
  34.                                 } catch (err:Error) {
  35.                                 }
  36.                         } else if (cnm == "Array") {
  37.                                 trace("array contents...");
  38.                                 try {
  39.                                         var ar:Array = o as Array;
  40.                                         trace("ar " + ar);
  41.                                         trace(ar.join("\r"));
  42.                                 } catch (err:Error) {
  43.                                         trace(err.toString());
  44.                                 }
  45.                         }
  46.                 }
  47.         } catch (err:Error) {
  48.         }
  49. }

AS3: recreating onLoad, MovieClip.addFrameScript part duex

Recreating onLoad with MovieClip.addFrameScript

AS2 had an onLoad event, called after the frame's children had been created, which is largely how custom components were able to manipulate the visual state for useful things like:

  • register listeners.
  • hiding and showing clips (especially when multiple clips are stacked on top of each other for different error messages)
  • jumping to a frame,
  • setting up modal dialogs/mouse opaque areas

In AS3 this doesn't work the same, and this bites you on the occassion you have an actionscript class is registered to a class via the linkageID, like that of the "Document class.
If you try it you get the warning:

Warning: 1090: Migration issue: The onLoad event handler is not triggered automatically by Flash Player at run time in ActionScript 3.0. You must first register this handler for the event using addEventListener ( 'load', callback_handler).

So you think, ok, easy enough. Just add an eventListener to load. Test movie and then nothing happens. Search the help docs, and you find zero references to 'load' event. Check DisplayObject and you find every other event (render, added, etc) than the one we are looking for. Leads to one of two possibilities. One the load event doesn't exist... or second the load event has already been called prior to the class constructor being called, so adding a reference to it won't actually do anything. I can't really tell, I've tried searching and running some tests, and can't get it to work... which leads me to the question, just how do I accomplish all those in items above?

Thankfully the fix is easy: addFrameScript in the constructor.

addFrameScript(0,onLoadHandler); //works!!!
// addEventListener("load", onLoadHandler); // doesn't work

While on the subject,  in order to access clips on the  timeline from the *.as they should be public variables like this:

  • public var my_btn:SimpleButton
  • public var my_playerControls:MovieClip

and your class should be dynamic e.g

dynamic public class MyView extends MovieClip

Keeping the scripts already on the timeline.

Since your replacing the script call when it hits frame0, whatever is on the timeline won't be called. Thankfully since MovieClip is a dynamic class, scripts on the timeline are appended to the prototype, thus available by this which refers to the prototype chain instead of the class inheritance (Flash is multiple inheritance!). This is useful when the timeline has some configuration data/setup on it, and this approach allows you to call it prior or after your onLoadHandler

public function MyMovieClipClass(){

super();
addFrameScript(0,onLoadHandler);
// addEventListener("load", onLoadHandler); //doesn't work
trace(" AAAA CONSTRUCTOR AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
function onLoadHandler():void{
trace("BBBBB onLoadHandler BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
trace("onLoadHandler " + currentFrame);

///pre frameactions

if(!this["frame1"]){
this["frame1"]();
}

//post frameactions

my_btn.addEventListener(.....

my_playerControls.gotoAndStop("buffering");

}

OUTPUTS

  • AAAA CONSTRUCTOR
  • BBBB onLoadHandler
  • CCCC frame1TimelineScripts

AS3: MovieClip.addFrameScript

MovieClip.addFrameScript is a very powerful way to decouple script from the timeline. This post help you understand how it works.

When movies are compiled (including the top level swf), if no class is provided. Flash creates one for you behind the scenes. 1 Class is created per 1 MovieClip, typically sharing the same name. It helps to understand how the conversion happens, to make best use of it, and writing code on the timeline in general.

  1. imports are only needed once in a timeline
  2. variables declared on a frame, end up as class variables,
  3. you can't redeclare variables on multiple frames.
  4. only one function is called per frame, this can be tweaked, overridden or amended with addFrameScript

You can't redeclare variables,

on frame1 and frame2 put:
var aName:String = "A";
will give you the following error:
1151: A conflict exists with definition aFrame in namespace internal.

however putting on Frame 1

var aName:String ="A"; //frame1

and on Frame2

aName = "B" //frame2

works fine.

MovieClip.addFrameScript

Is the undocumented glue between the timeline frame and the class's function ( if bound). There can only be one script/function bound to a frame using the built in addFrameScript, though an alternative approach is to poll currentFrame changing (via Event.ENTER_FRAME).

This binding happens via addFrameScript(frameNumber-1, functionToCall...); Note that the first argument is ZERO BASED (while normally the timeline is 1 based)
During the Fla.compilation our script gets put into a function like:

Class Test_fla{

function frame1():*{

var aName:String = "A";

}

function frame2():*{

aName = "B";
}
}

The important thing to note is the frame1...frameN naming, where N is the frame number 1-whatever, We can validate this by decompiling the swf, using the AbcDump utility, which takes our test.swf and outputs:

function Test_fla::frame1():* /* disp_id 0*/
{
// local_count=1 max_scope=1 max_stack=2 code_len=16
0 getlocal0
1 pushscope
2 findpropstrict trace
4 pushstring "onFrame1"
6 callpropvoid trace (1)
9 findproperty aFrame
11 pushstring "aFrame"
13 initproperty aFrame
15 returnvoid
}

So the end will be a frame to function mapping (which we can't see) like:

  • [0]= frame1()
  • [1]= frame2()

Note again the frameNumber is zero based, while the script is 1 based.

Consequences

which has 2 consequences,

  1. you can't name your own functions frameN if one already exists. This is the same error as compiling in the same *.as:
    function frame1(){

    }

    function frame1(){

    }

  2. you can call and over ride these functions,
    addFrameScript(0, overrideFrame1);
    function overrideFrame1():*{

    //do something before
    frame1();
    //do something after
    }

In addition the top level Class, will be shared across different scenes, but these can have on the timeline different configuration values (say which language to use), and when used with jsfl to publish all scenes, create a handful of starter files.

Conclusion

This is kick ass for completely separating a UI.swf from a Controller.swf Keeping designers working on the timeline, and coders out of the design.

For the coders, and others working in Flash, is the ability to use addFrameScript to perform actions when a frame is fully initialized and children are on stage, similar to onLoad() in AS2, where-as use of ENTER_FRAME gets called prior to children in that movieclip getting called...this is similar to onInit() in AS2. So for example you can have a button only on a particular frame and setup it's event listener to call your controller...in another swf, regardless of what the playhead is doing.

In someways I find this cleaner than watching using addedToStage, rendered etc.

REFERENCES:

[1] Senoculars post
[2] FlashGurus info as Senocular
[3] BIG SPACESHIP, Extending addFrameScript to use the frame labels instead of framenumbers... the same glue I'm using as well, though I'm more interested in events being generated, than script.

[4] an alternate approach on evilzug's journal using ENTER_FRAME


You should be aware if using that approach that when the framescript is called via the ENTER_FRAME, items in the frame may have not been added yet, so this approach can't be used to bind to objects on the timeline (e.g. to registerListeners to buttons), this the addFrameScript does not suffer from.

Here's an output with one on the timeline the other added via oizys's frameScript approach:


entered frame 3
onReachedInstructions instructions null <- no button yet, via modified addFrameScript
timeline reached instructions [object SimpleButton] <- normal addFrameScript
onReachedInstructions instructions [object SimpleButton] <- now the button exists

AS3 and Serialization, Memento Pattern

Starting to work on porting the persistence aspect of Prevayler to Flash/AS3. It's a perfect fit with Flash, as Prevaylence assumes that everthing can fit into RAM (which flash does, aside from loading new things in), and changes have to be synchronized, which seeing as it's a primarily a single thread doesn't have to worry about synchronizing multiple writes.
Prevaylence relies heavily on understanding the serialization of objects, both the commands and the business object state should be save and restoreable. Since ByteArray supports AMF packets (as does shared object) this means that private vars and Class definitions, functions should be able to be written to disk and brought back from dead...but this leaves the larger question, just like the dinosaurs ...should they be brought back?
It's certainly tempting.  Java has faced this for years in it's serialization routines. In general built in serializing is good, but doing it across version is bad, as it's inevitable that the Class definition will change, adding, removing variables and methods ...even changing types, during refactoring. When reconstituting things may not serialize properly much as you don't fit into your GI Joe pajamas from kindergarden (well hopefully). It's unpredictable what the Flash player will do when forced to put version 1.2 into 1.3 shoes, or potentially version 1.4 into 1.2.  So it's important to know exactly what is being saved and how flexible it is.

Since I was working on Trace.me this week, and private, protected, etc variables didn't show up in the output, (which is proper Object encapsulation), I was curious as to if the variables were invisible or lost. I remembered  hitting similar things when working with and on openAMF, so I dug a bit, and just found a document I wrote a few years ago, on openAMF, ah good times...

http://openamf.cvs.sourceforge.net/*checkout*/openamf/openamf/docs/understanding.htm?revision=1.1

There the issue there was different, was actually whether java's accessors e.g. get someValue and set someValue translated across the wire. The serialization in Java openAMF calls the accessors and puts them into a hashmap, before sending across the wire. Flash does something similar when sending back to the server, skipping the accessors as back then it didn't really know what those were. But I wasn't sure if this was the case when targetting ByteArray and Shared Local Object for output.

I have an dummy object called IntrospectableObject, it has all manner of public, private, protected etc vars of all different types (well at least enough to test), I've been using to test. I've added a few accessors and a toString method which outputs everything.

The code to test ByteArray as a serialization is simple

import flash.net.SharedObject;
import com.troyworks.prevayler.*;
import flash.utils.ByteArray;
import flash.net.registerClassAlias;
import flash.utils.describeType;

////////////// BYTE ARRAY TESTS ////////////////

var bA:ByteArray = new ByteArray();
//registerClassAlias("com.troyworks.prevayler.IntrospectableObj", IntrospectableObj);
var iObj:IntrospectableObj = new IntrospectableObj();

bA.writeObject(iObj);
bA.position = 0;
var aObj:Object = bA.readObject();
trace("aObj " + aObj.toString());

trace("XML " + describeType(iObj).toXMLString());
trace("attempting to introspect ");
for (var i:String in aObj){
trace( i + " " + aObj[i]);
}

It outputs:

new Introspectable //consturctor of source
aObj [object Object]
attempting to introspect
pubStrVar public testString
pubBooTrueVar true
pubVar undefined
pubUIntVar 1
pubIntVar -1
pubNumVar 0.5
pubBooFalseVar false
pubDateVar Wed Aug 29 02:40:48 GMT-0700 2007

which reveals no accessors are called, and nothing but public vars are called, which is to be expected. Uncommenting:

registerClassAlias("com.troyworks.prevayler.IntrospectableObj", IntrospectableObj);

We get something completely different. As it's creating a new class upon reconstitution, and then calling it's toString() which has access to the private/internal so can see more. This means that ByteArray does get the whole picture even if it doesn't share all of it.

new Introspectable //constructor of source
new Introspectable  //constructor of reconstructed
aObj pubStrVar public testString
pubNumVar 0.5
pubIntVar -1
pubUIntVar 1
pubBooFalseVar false
pubBooTrueVar true
pubDateVar Wed Aug 29 03:00:12 GMT-0700 2007
priStrVar priTestString
priNumVar 0.2
priIntVar 2
priUIntVar 2
priBooFalseVar false
priBooTrueVar true
pritDateVar Wed Aug 29 03:00:12 GMT-0700 2007
attempting to introspect
--none--- //can't see anything as it's now class based.

What interesting is simliar to AMF, the constructor is called by Flash/AS3, but the values of the items as serialzed overwrite, bypassing the encapsulation layer (and thus any events). This is good if sometimes tricky to remember. It's resetting the snapshot of the object's state. Which compared to broadcasting a dozen+ change events makes sense, as it really hasn't changed, it's just been restored. This is basically what we want but as prior this may hit the shoes don't fit problem, break on newer swfs with different code coming out.

What this indicates to me is that serialization for Prevayler like approaches, should be done via the memento pattern, and only the mementos should be serialized. Wikipedia has a good article on it.
http://en.wikipedia.org/wiki/Memento_pattern  which interestingly links to a large page on finite state machines. which uses the memento pattern to save state/shallow history, with extended variables.
http://en.wikipedia.org/wiki/Finite_state_machine

Both the business logic and the commands should implement the pattern.  Mementos immutable read-only ideally, with all values passed into the constructor and only accessible via getters. But if the number of attributes are large, I could see a Object/struct with purely public variables may be better for useability and performance, as Mementos shouldn't do anything. In addition, Mementos should have a version number of the 'file format', and preferrably some mechanism can faciliate putting the data in via calling getters on the originator object, if they aren't marked as non-serializable (e.g. calculated results like totals shouldn't be serialized). Which I often like to put into a separate class, as the serialization/deserialization may be for different formats, XML, and they aren't really 'business object' concerns. One could even use the namespace feature to manage which one is being used.
This is exciting to me as Shared Local Object is cheap and fast and shared..a great place for multiple flash movies potentially in different web pages, maintaining a common log. Inside AIR's using it's DB, is another good place to store offline/online commands that get synched back up..potentially against other users making changes at one, when connected again.
This would take Flash RIA's closer to conventional applications, even if they are microcontent. It's always annoyed me how long running flash games how some don't remember where you are, using cheat codes so you can jump to a particular level, and that undo is so problematic, so it's either being petrified to explore for fear of making a mistake or having to do the same thing over and over again. There are many games I'd play longer if these capabilities existed. Flash makes these features easy,  it's just that most applications aren't written to support.
Since ByteArray can be compressed and written to Strings, this means they can be files, and thus to disk somewhere, be it SaveAs in AIR,  POST, XML. Sockets, meaning that a saving a application and then logging into another machine and getting everything back would be easy, or potentially opening and saving multiple versions so people can try things or create things easily.

ByteArray brings back for…in, and Trace.me

Neato!

one of the useful utilities for AS2 for me was util.Trace.me which basically iterated over an object and gave you an output similar to (apologies WordPress is mangling my indentation, this is 3 layers deep):

[[[[ MyObject.displayObjmeAsArray() START nested: true ]]]]
+- a4:string = test
+- a2:Array = ,[object Object]
+-- 1:object = [object Object]
+- a5:object = [object Object]
+- a6:string = test
+- a8:string = test
+- a3:string = test
+- a10:object = [object Object]
+-- b1:object = [object Object]
+- a13:object = [object Object]
+-- a4:string = test
+-- a2:Array = ,[object Object]
+-- a5:object = [object Object]
+-- a6:string = test
+-- a8:string = test
+-- a3:string = test

this was invaluable when using SOS for getting to the bottom of an issue when the normal flash debugger wasn't an option. But of course with AS3 this really doesn't help as things are class based and you get pretty much nothing (or whatever describeType gives). But I figured out a workaround.
util.Trace.me has moved to com.troyworks.util.Trace, pass it a class and you will get a introspected view like:

+- pubUIntVar:uint = 1
+- pubNumVar:number = 0.5
+- pubIntVar:int = -1
+- pubBooTrueVar:boolean = true
+- pubStrVar:string = testString
+- pubBooFalseVar:boolean = false
+- pubDateVar:Date = Tue Aug 28 00:56:59 GMT-0700 2007
+- $$_id_:uint = 0

This is similar to for...in over normal objects/structs in AS2.

Note: only public vars (not static, const) are retrieved via this.

Note: $$_id_ isn't a part of the object, it's a remnant tag, is used to prevent circular infinite loops, marking the chain, since it's clone of the object, be it class or object based shouldn't be an issue, but not entirely sure how clone works with circular references/dependencies.

How it works is ByteArray to clone an Class which strips out the class definition, leaving it easy to traverse Object with a for...in loop.

What confuses me is how AMF serializes private data, yet ByteArray when back and forth doesn't. I can't tell if this is just security or a limitation.

« Previous Entries