Saturday, April 16, 2011

Using AS2 onEnterFrame to speed the display of MovieClips

ActionScript 2 broadcasts the onEnterFrame event regularly. If a movie is running at 24 frames per second, you would expect to see it every 1000/24 milliseconds, or about every 42 ms. By using traces like


trace("starting frame at "+(new Date()).getTime());


you can actually measure the passage of time.

It turns out that like many of our 7.5 hour work days, a frame actually can last a lot longer than its appointed 42 ms, if there is a lot of work to get finished. Recently, I have been working on a tool that graphs skips on a number line. Operations are menu-driven. The tool currently has about 4000 lines of code, the menu another 1600 and a variety of other classes are used to create buttons, text, stepper widgets etc. In all, it takes about 2500 ms to display. If all of that code executes on one frame, the wait is agonizing. Nothing is displayed until all the code is executed.



There are ways to tell Flash that it can pause and update the screen. One is to break up the code into functions that are called with onEnterFrame.


private function step1():Void{
   this.drawContainer();
   this.createEmptyMovieClip("timer", this.getNextHighestDepth());

   this.timer.onEnterFrame = function(){
      this._parent.step2();
   }
}
private function step2():Void{
   this.drawTool();

   this.timer.onEnterFrame = function(){
      this.parent.step3();
   }
}
private function step3():Void{
   delete this.timer.onEnterFrame;
   this.drawMenu();
}


and another is to use setTimeout, which unfortunately was left out of the list of keywords for AS classes and must be envoked in a strange way.

_global["setTimeout"](this, "step2", 0);


In my tests, I could not see that either approach was better and stuck with the onEnterFrame approach, since it was the one I tried second. Now, I see a container on the screen as soon as step 1's code is done, after about 40 ms. I see the number line and some options next and finally I see the menu appear. The illusion of progress is very satisfying compared to waiting the full 2500 ms for the container to appear. The screen doesn't actually change after about 900 ms, since a lot of the work is to set up subMenus and configuration panels that are not immediately visible. Each of these frames take significantly longer than 42ms to perform, but giving pause after each visible change provides a much better result.

Generally, it is a pain for a class to take up more than one frame's worth of code, since the programmer would have to wait to customize or work with the instance. This is mitigated by having the class broadcast an event when it is all done.


this.broadcastMessage("onSkipCountingToolReady", this);


The program can listen for the event and then perform subsequent operations, without having to guess how many frames or how many milliseconds to wait before it is safe.

Thursday, March 10, 2011

How can you make an Actionscript 2 function know about itself? How do you copy all functions from one movieclip to another?

Scoping has got to be one of the most tricky things in programming in Actionscript. A function like:
this.shape_mc.onPress = function(){
trace(this);
}
will trace out the path to this.shape_mc when shape_mc is pressed, unless call or apply is used. If the same function above is invoked using call:
this.shape_mc.onPress.call(this.shape2_mc);
the path to this.shape2_mc will be traced!

Recently, there have been a number of times when I want a function to know about itself, the way shape_mc knows about itself as "this" in the function above. Here is a simple example of how that can be done:
//create a triangle
this.createEmptyMovieClip("triangle_mc", this.getNextHighestDepth());

this.triangle_mc.beginFill(0x00a900, 100);
this.triangle_mc.lineStyle(1, 0xFF0000, 100);
this.triangle_mc.moveTo(50,0);
this.triangle_mc.lineTo(100, 83);
this.triangle_mc.lineTo(0, 83);
this.triangle_mc.lineTo(50,0);
this.triangle_mc.endFill();

this.triangle_mc.onRelease = function(){
this._parent.traceEvent.call(this, arguments.callee.name);
}
this.triangle_mc.onReleaseOutside = function(){
this._parent.traceEvent.call(this, arguments.callee.name);
}

this.triangle_mc.onRelease.name = "onRelease";
this.triangle_mc.onReleaseOutside.name = "onReleaseOutside";

this.traceEvent = function(type:String){
trace("An event of type:"+type+" happened to "+this);
}

From which you get output like:

An event of type:onRelease happened to _level0.triangle_mc
An event of type:onReleaseOutside happened to _level0.triangle_mc

There are three subtle things going on here. One is that arguments.callee is a self-reference to the function that is being executed. The second is that a function can be assigned properties, like name. Lastly, using call with traceEvent means that the "this" is changed from "_level0" to the first parameter in the call statement, in this case, _level0.triangle_mc.

You can find examples on the web where arguments.callee is used to implement recursion, like this one where the names of all functions are set recursively.

You can even set a property on a function to be another function, like

this.triangle_mc.onRelease.tracingFunction = this.traceEvent;

In which case the onRelease function could be:

this.triangle_mc.onRelease = function(){
arguments.callee.tracingFunction.call(this, arguments.callee.name);
}

Getting dizzy yet?

Like call, apply lets you set the scope of a function, but the parameters are supplied in an array. The following example is the definition of a function in a class ubMath called coordsToGlobal which is used in the documented example to draw a bounding rectangle around one movieClip on another one.

/**returns an array of global (stage coordinates)
*@param mc (MovieClip) the movieclip that the coordinates are respect to.
*@param x (Number) the x coordinate in mc's coordinate system
*@param y (Number) the y coordinate in mc's coordinate system
*@param global_mc (MovieClip) optional movieclip to express the coordinates with respect to. If undefined, it will truly be global.
*@returns (Array) [x, y]
*@example

import edu.clips.util.ubMath;

var boundsObj:Object = mc.getBounds();
var container:MovieClip = _root.createEmptyMovieClip("ubDebug_bounding"+(new Date()).getTime()+"_mc", _root.getNextHighestDepth());
container.lineStyle(3, 0xFF0000, 70);
container.moveTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMin, boundsObj.yMin));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMax, boundsObj.yMin));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMax, boundsObj.yMax));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMin, boundsObj.yMax));
container.lineTo.apply(container, ubMath.coordsToGlobal(mc, boundsObj.xMin, boundsObj.yMin));

*/
public static function coordsToGlobal(mc:MovieClip, x:Number, y:Number, global_mc:MovieClip):Array {
var myPoint:Object = {x:x, y:y};
mc.localToGlobal(myPoint);
if (global_mc != undefined) global_mc.globalToLocal(myPoint);
return [myPoint.x, myPoint.y];
}


You can also use arguments to pick up the parameters passed to a function. In the following example a function is created on newMc for every function in mc. The new function calls the old function on mc, scoped with newMc, with all the parameters that got passed to it.

for (var item in mc) {
if (typeof(mc[item]) == "function"){
newMc[item] = function(){
var argArray:Array = new Array();
for (var i=0; i<arguments.length;i++){
argArray.push(arguments[i]);
}
return arguments.callee.functionDuplicatedFrom.apply(this, argArray);
}
newMc[item].functionDuplicatedFrom = mc[item];
}
}


Oh no, I've said too much
I haven't said enough...

Friday, October 29, 2010

Why can't my new Dell compile Flash CS4 files faster?



All the serious geeks at Adobe must use Macs.

I have a new Dell Studio XPS 1645 with 8GB of RAM, an i7 Q820 processor @ 1.73GHz, and Windows 7 Ultimate 64 bit OS (giving it a 5.9 Windows Experience score). It takes 39 seconds for this beast to compile one of our CLIPS activities. My confreres running Macs get it done in about 15 seconds. Today, I tried compiling the same .fla on a 4.5 year old MacBook (not a Pro) and it took 18 seconds. Then I tried my 3.5 year old Dell running Windows XP and it took 28 seconds.

Why doesn't an upgrade feel like one?

I have looked around the web for ideas about how to get Flash to compile an .fla faster but haven't found anything helpful. Maybe I should use the XP virtualization that came with the Windows 7 machine or use DropBox to distribute my computing power.

Does anyone have any similar experiences and especially any that will speed up the process?

Sluggish and Lonely in PC land,

Mathfester

Tuesday, May 25, 2010

Should have stuck with talking about goalies!

I remember a Euclid Problem that was referred to as "that old chestnut - the goalie problem". This teacher should have stuck to talking about goalies...

Thursday, May 20, 2010

Literacy, Student Engagement, Images and TED

Jesse Brown, host of TVO's Search Engine podcast, spoke at the recent TEDxOntarioEd event in London. It is kind of ironic that I listened to this talk on Rodd Lucier's Clever iPod app, given its heavy reliance on visuals. I did this on the same day that I received word that one of our innovative teachers, Lynda Marshall was awarded a Literacy prize from Nipissing University, in part for her work with graphic novels.

One of my former students, David Kennedy, has been working on Bitstrips as well. Bitstrips for Schools is licensed by the Ministry of Education for Ontario's publicly-funded schools.

Enjoy!

Wednesday, May 19, 2010

Computation is destined to be the Defining Idea of Our Future

Is Stephen Wolfram a Clive Cussler-esque egomaniac or a visionary? When do we tell students about this - or are we too busy hoping they don't find out that Wolfram Alpha factors trinomials?

TLDR

Will Richardson mentions this recently.

I have been thinking a lot about how learning objects like CLIPS can be precise and concise at the same time.

Did you get through all that?