Wednesday, October 26, 2011

Improving MovieClipProxy.as?

This is the fifth post in a series.  I made a couple of  "improvements" to MovieClipProxy.as and found out a thing or two before abandoning the idea due to our sloppy coding practices that would require more fixing than I was prepared to do.

I was also happy to receive a reply from Senocular indicating that there was not a simpler way just to override the _visible setter.  He wrote:
AS3 supports native implementation overrides for getter/setters, but not AS1/AS2
In the last post, I left off wondering whether I was willing to write a getter/setter for all the properties, methods and functions of my ubPod class.

I wrote two functions for MovieClipProxy.as to add additional properties and functions to a MovieClipProxy instance, namely addProp and addFunction:

public function addProp(propName:String){
 var getterFunction:Function = function(){
  return this._mc[arguments.callee.name];
 }
 getterFunction.name = propName;
 var setterFunction:Function = function(newVal):Void{
  this._mc[arguments.callee.name] = newVal;
 }
 setterFunction.name = propName;

 this.addProperty(propName, getterFunction, setterFunction);
}
public function addFunction(propName:String){
 this[propName] = function(a, b, c, d, e, f, g, h, i, j){
  return this._mc[arguments.callee.name].call(arguments.callee.mcp_instance, a, b, c, d, e, f, g, h, i, j);
 }
 this[propName].name = propName;
 this[propName].mcp_instance = this._mc;

 //no luck getting getters and setters to work for functions
    /*
 var getterFunction:Function = function(){
  trace("in mcp getter function with name "+arguments.callee["name"]+" from "+arguments.callee);
  trace("addFunction getter");
  return this._mc[arguments.callee.name];
 }
 getterFunction.name = propName;
 var setterFunction:Function = function(f:Function){
  trace("in mcp setter function with name "+arguments.callee["name"]+" from "+arguments.callee);
  trace("addFunction setter");
  //this._mc[arguments.callee.name] = newVal;
  this._mc[arguments.callee.name] = function(a, b, c, d, e, f, g, h, i, j){return f.call(arguments.callee.mcp_instance, a, b, c, d, e, f, g, h, i, j)};
 }
 setterFunction.name = propName;
 setterFunction.mcp_instance = this._mc;

 this.addProperty(propName, getterFunction, setterFunction);
 */
}

which are invoked pretty simply with code like:

mcp.addProp("side");
mcp.addFunction("scale");

Next, I wondered if there was a way to get my ubPod class to reveal more of its properties and methods in a for..in loop. The new learning here relates to ASSetPropFlags (of which the compiler is unaware) which allowed me to write a function like:

_global["ASSetPropFlags"](ubPod.prototype,null,6,true);

for (var i in ubPod.prototype){    
    var thing = ubPod.prototype[i];
    var typeThing:String = typeof(thing)
    if (typeThing == "function" && i != "__constructor__"){
        mcp.addFunction(i);
    } else if (typeThing == 'number' || typeThing == 'string' || typeThing == 'boolean' || (typeThing == 'object' && i != "__proto__")){
        mcp.addProp(i);
    } else {
        trace("did nothing for "+i);
    }
}

Maybe some of this will help folks still in AS2 land.  I am left still pretty foggy about __proto__, prototype, extends and casting.

Here are two challenges:
  1. Find the error in the code shared in the previous post.
  2. Modify my addFunction method to allow for a getter and setter as well as a function definition to be established.

Monday, October 24, 2011

Actionscript 2: Catching when an instance of a Class that extends MovieClip is made visible

You just don't get blog post titles as precise as that every day!

I have been telling a story today about trying to catch when a movieclip's _visible property is set.  First, I showed how we create classes that extend MovieClip.  Next, I described the problems that I am having writing a set and get function for _visibleThird, I showed how MovieClipProxy could be used on a simple MovieClip to catch the setting of _visible.  Now, I am going to finish the story and show how it can be done for a class like ubSquare.

The first step is to make a slight adjustment to MovieClipProxy.   Its constructor is written to either take 3 parameters and create an empty MovieClip or 4 parameters and attach a movieclip from the library.  I changed it so that if it is sent more, it will use the first parameter to create the movieclip.

In the first post's example, we created the ubSquare using:

this.mySquare_ubS = ubSquare.create(this, "mySquare_mc", {_x:50, _y:75, _side:10});

So if we change the constructor of MovieClipProxy to read:

 function MovieClipProxy(a,b,c,d,e) {
  var mc, mc2;
  if (arguments.length == 3) {
   mc = __initEmpty(a,b,c);
  } else if (arguments.length == 4){
   mc = __initAttached(a,b,c,d);
  } else {
   mc = a(b,c,d,e);
  }
  __applyPrototyped(mc);
 }

Then we can invoke it with:
 var mcp:MovieClipProxy = new MovieClipProxy(ubSquare.create, container, instanceName, initObj, depth);

and the created MovieClip will now be a ubSquare.

But, ubSquare had a side property and a scale method that are not handled by MovieClipProxy.  I can create a ubSquareProxy Class

import edu.clips.test.MovieClipProxy;
import edu.clips.test.ubSquare;

class edu.clips.test.ubSquareProxy extends MovieClipProxy {
 
 public static function create(container:MovieClip, instanceName:String,
         initObj:Object, depth:Number) {
  if (depth == undefined) depth = container.getNextHighestDepth();
  var mcp:MovieClipProxy = new MovieClipProxy(ubSquare.create, container, instanceName, initObj, depth);

  //define all the ubSquare properties, methods and functions
  mcp["scale"] = function(newScale:Number){
   trace("in ubSquareProxy.scale "+ mcp._mc);
   mcp._mc.scale(newScale);
  }
  mcp.addProperty("side", 
      function(){trace("addProperty getter");return this._mc.side}, 
      function(newSide){trace("addProperty setter");this._mc.side = newSide});
  return mcp;
 }
}

and create it using:

this.mySquare_ubS = ubSquareProxy.create(this, "mySquare_mc", {_x:50, _y:75, _side:10});

Now the questions that I am left with are these:
  1. Is it worth the time to apply this method to my ubPod class with 80 documented properties, 23 methods and 7 event handlers just to capture when the one MovieClip property, _visible, changes?
  2. Is there a way to use __proto__ or prototype or constructor to access the MovieClip's setter for _visible and just change that to broadcast an event?
  3. Should ubSquareProxy extend MovieClipProxy, MovieClip or nothing?

Actionscript 2: One Way to catch when a MovieClip becomes visible

In my previous post, I outlined the difficulties that I have been having reacting to when a MovieClip becomes visible.  I think the big reason for these difficulties is that _visible is a MovieClip property that is impossible to override with getters and setters.

However, I happened to be trolling around Senocular's site and found his MovieClipProxy class.  This class creates an object with all of the standard MovieClip properties with getter and setter functions and all the standard MovieClip methods together with the standard functions, like onEnterFrame, envoked using the call statement and the correct scope.

If I change the setter and getter for _visible as follows:

 function get _visible():Boolean {
  trace("get _visible");
  return _mc._visible; 
 }
 function set _visible(v:Boolean):Void {
  trace("set _visible");
  this._xscale *=2;
  _mc._visible = v; 
 }

Note that I am just setting the _xscale when _visible is set just for a double visual confirmation.

If you create a new Actionscript 2 .fla tester file with the code:

import edu.clips.test.MovieClipProxy;

var mcp:MovieClipProxy = 
 new MovieClipProxy(this, "test", 1);

trace("_xscale before first set _visible "+mcp._xscale);
mcp._visible = false;

mcp.clear();
mcp.lineStyle(2, 0x0000FF, 100, true);
mcp.moveTo(0, 0);
mcp.lineTo(200, 100);

trace("_xscale before second set _visible "+mcp._xscale);
mcp._visible = false;
trace("_xscale after second _visible "+mcp._xscale);

this.test._visible = true;
trace("_xscale after movieClip set _visible "+mcp._xscale);
trace("or "+this.test._xscale);

and test it, you get the output:

_xscale before first set _visible 100
set _visible
get _visible
_xscale before second set _visible 200
set _visible
get _visible
_xscale after second _visible 400
_xscale after movieClip set _visible 400
or 400

When you reference the MovieClipProxy object mcp, the setters and getters are used and applied to the  MovieClip this.test.  When you reference this.test._visible directly, the setter is not used.

Now you are in the position to capturing the setting of _visible and do something more sensible than upscaling, as long as you create a MovieClipProxy and reference it.

This still leaves the question of whether this approach can be bolted on to the method we are using to create Classes that extend MovieClip.


Actionscript 2: Trouble overriding setter for MovieClip Properties like _visible

In my previous post, I described how we create classes to extend MovieClip.

Continuing with the ubSquare example, now let's suppose that I want to scale my square whenever it becomes visible.  Well, _visible is a property of the class, just like side was so how about adding some code like:

//at the top with _side
private var __visible:Boolean = true;

//with the side getter and setters
public function get _visible():Boolean{
 trace("in ubSquare get _visible");
 return this.__visible;
}
public function set _visible(newVis:Boolean):Void{
 trace("in ubSquare set _visible");
 this.__visible = newVis;
 this._visible = newVis;
}

This is just like what I did for side, except that I can't just change my private __visible property and expect the movieclip to change, so I add the last line to tell the movieclip to actually change its visibility.

There are two problems with this:
  1. If I put a line like:
    this.mySquare_ubS._visible = false;
    in my tester, the set function is not called.
  2. If I put a line like
    this._visible = true;
    in my scale method, I get 256 levels of recursion since the set for _visible calls itself.
One way to fix the second problem is to change the last line of the set _visible function from:
this._visible = newVis;
to
setProperty(this, _visible, newVis);

The output becomes:

scale making movieclip visible true
in ubSquare set _visible
in ubSquare get _visible

This, of course, begs three questions:
  1. what is the point of writing a setter for _visible if it can be avoided by using a (deprecated) setProperty operation?
  2. why is a get _visible call made when I set _visible? The line in the scale method (this._visible = true) calls both the setter and the getter.  I was sure I had a good link that explained why but cannot for the life of me find it now.
  3. how do I solve the first problem which was the important one?  I want to be able to notice when anything makes my square visible, not just when some of my own class code does.
I have figured out one way to catch the setting of _visible for a MovieClip, which I will blog about next, but have yet to figure out how to apply it to a Class that extends MovieClip.

Actionscript 2: Extending MovieClip class without using a Library Object

Thanks to Wouter Verweirder, we are creating all of our classes for the CLIPS project without requiring library objects.

The following is a short example of a class that creates a red square, the size and scale of which can be changed.  The size is changed either through the private property _side or the virtual public property, side.  The scale is changed by a public method, scale().

/**This class creates a square on the stage.
It is purely illustrative.  

*/

class edu.clips.test.ubSquare extends MovieClip {
 //used to register this class as though it was in the Library
 static var symbolName:String = "__Packages.edu.clips.test.ubSquare";
 static var symbolOwner:Function = ubSquare;
 static var symbolLinked = Object.registerClass(symbolName, symbolOwner);

 /**the length of each side, in pixels.  Default: 20 */
 private var _side:Number = 20;
 
 public static function create(container:MovieClip, instanceName:String,
         initObj:Object, depth:Number) {
  if (depth == undefined) depth = container.getNextHighestDepth();
  trace("attaching ubSquare using the manufactured symbolName");
  container.attachMovie(symbolName, instanceName, depth, initObj);
  trace("casting MovieClip as ubSquare");
  //this avoids compiler errors in other classes involving ubSquare
  return ubSquare(container[instanceName]);
 }

 function ubSquare() {
  trace("in ubSquare");
  this.constructor = ubSquare;
  this.constructor.name = "ubSquare";
  this.drawSquare();
 }
 public function onLoad():Void {
  trace("ubSquare loaded");
 }
 public function get side():Number{
  trace("in ubSquare get side");
  return this._side;
 }
 public function set side(newSide):Void{
  trace("in ubSquare set side");
  var numSide:Number = parseInt(newSide, 10);
  if (!isNaN(numSide)){
   this._side = newSide;
   this.drawSquare();
  }
 }
 public function scale(newScale:Number){
  //this will use set side
  this.side *= newScale;
 }

 private function drawSquare():Void {
  //this won't use the set and get functions, since _side is used
  trace("in drawSquare");
  this.clear();
  this.lineStyle(2, 0xFF0000, 100, true);
  this.moveTo(0, 0);
  this.lineTo(0, this._side);
  this.lineTo(this._side, this._side);
  this.lineTo(this._side, 0);
  this.lineTo(0, 0);
  trace("finished drawSquare");
 }
}

and here is the code in the first frame of a new actionscript 2 .fla that uses it.  Note that you have to save the class in a path ending with edu/clips/test/ubSquare.as and set your Actionscript preferences to be able to find it.

import edu.clips.test.ubSquare;

this.mySquare_ubS = ubSquare.create(this, "mySquare_mc", {_x:50, _y:75, _side:10});

trace("tester settting virtual property side");
this.mySquare_ubS.side = 40;

trace("tester calling public function scale");
this.mySquare_ubS.scale(3);

trace("the side ends up being: ");
trace(this.mySquare_ubS.side);

Here is the output:

attaching ubSquare using the manufactured symbolName
in ubSquare
in drawSquare
finished drawSquare
casting MovieClip as ubSquare
tester settting virtual property side
in ubSquare set side
in drawSquare
finished drawSquare
in ubSquare get side
tester calling public function scale
in ubSquare get side
in ubSquare set side
in drawSquare
finished drawSquare
in ubSquare get side
the side ends up being: 
in ubSquare get side
120
ubSquare loaded

In most of our stuff, we don't use setters and getters at all. We initialize the properties by passing them in the third parameter of create, as I did to initially set the side to 10 px  (see Paul's post).  We change properties using a setProp method that would look something like:

public function setProp(prop:String, val, doDraw:Boolean):Void {
 if (doDraw == undefined) doDraw = true;
 this[prop] = val;
 if (doDraw) this.drawSquare();
}

Senocular's FAQs were also very useful when we were trying to figure out how to extend the MovieClip class.

Friday, October 7, 2011

Tuesday, October 4, 2011

My Buddy Doug

Although, I am a little behind in my reading of Doug Peterson's blog, he always has fascinating links and articles to dig into.  Yesterday, it was the Dolphin Mobile browser and a post about Blogger Mobile layouts.  The latter had me blow away the widgets on this blog for a few minutes until I found the revert button.

Today, was a fun diversion that appealed to my vanity.  When we were growing up, we thought we were the only Iseneggers in the new world, but the internet and Nadine cured that notion.

HowManyOfMe.com
LogoThere are
1
or fewer people with the name Ross Isenegger in the U.S.A.
How many have your name?

Doug is so current that he has dropped Mathfest off his blogroll, presumably for my dismal rate of posting.

More trivia from HowManyOfMe.com:
  • There are 78,058 people in the U.S. with the first name Ross.
  • Statistically the 622nd most popular first name.
  • More than 99.9 percent of people with the first name Ross are male.
  • There are fewer than 116 people in the U.S. with the last name Isenegger.


Monday, September 19, 2011

Flash reverts MovieClips to Original Negative Depth when moving to a Previous Frame

I have been working with Flash for over six years and there are still things that surprise me about Actionscript 2 - surprise but not delight!  This week, I was trying to create a new layer in between two existing ones on the stage, using code only.  I figured that I could create a new MovieClip and swapDepths to put it in the correct location.  Everything worked great except that my created MovieClip would disappear as I navigated to a previous frame.

It bothered me so much, that I created a very simple tester of the problem.  It has an actions layer and one layer with a thick red line drawn on it.  It has a text box that reports which of the four frames you are on.  It has a green button which goes to the next frame and a red button which goes to the previous frame.

On the first frame, I create an empty MovieClip with code, swap its depth with the stage MovieClip and draw a blue line on it.  Everything looks great as you go from Frame 1 to 2 to 3 to 4, but go back from Frame 3 to Frame 2 or from Frame 4 to Frame 3 and the blue line disappears.  If you trace the depth of the stage MovieClip, you see that Flash re-establishes it at its original depth when you navigate to the previous frame. Since your code-created MovieClip is at that depth, it gets wiped out. At least this is my current explanation.

Maybe you have a better theory and want to download the .fla to check it out.  Regardless, I currently think that there is no good way to simulate a timeline layer with code.

The code is:

stop();
trace("library_mc starts at depth: "+this.library_mc.getDepth());
this.library_mc.lineStyle(3,0xFF0000,100);
this.library_mc.moveTo(50,100);
this.library_mc.lineTo(200,150);
if (counter == undefined) counter = 0;
counter++;
trace("creating code_mc for the time: "+counter);
this.createEmptyMovieClip("code_mc", this.getNextHighestDepth());

this.code_mc.swapDepths(this.library_mc);

this.code_mc.lineStyle(1,0x0000FF,80);
this.code_mc.moveTo(200,50);
this.code_mc.lineTo(50, 150);

green_btn.onRelease = function(){
	this._parent.nextFrame();
}

red_btn.onRelease = function(){
	this._parent.prevFrame();
}
frame_txt.text = "I am on frame 1";
this.onEnterFrame = function(){
	frame_txt.text = "I am on frame " + _currentFrame;
	trace("library_mc depth "+this.library_mc.getDepth());
}

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...