Home Contact

[September 26, 2005]

The flash.geom.Point Class is Slow

Filed under: ActionScript — @ 3:01 pm

… at least on my system. This past week I decided to upgrade my flocking simulation project to use the Flash 8 Point class. I had a 2D coordinate class, as I’m sure many people do, and I expected I’d retire it. But it turned out that changing over to flash.geom.Point slowed the app down considerably.

Here you can download a MyPoint class that conforms to the flash.geom.Point API, and the PointTest class I used to compare its performance to that of flash.geom.Point. I built the program with MTASC and with Flash 8. Both swfs had the same dimensions and fps.

Windows XP Pro, Pentium 4 2.80 GHz, 2GB RAM, Flash 8 standalone player

Average times in milliseconds: 5 runs of each swf, iterations = 10000
(Equals test: 10000 iterations with equal Points, 10000 iterations with unequal Points)

		swf built by:	MTASC			Flash 8
				------------		------------
		Point class:	flash	mine		flash	mine

TEST
Interpolate			520	349		524	357
Distance			724	154		711	166
Polar				557	363		548	379
Constructor			249	185		282	180
Clone				412	285		441	309
Offset				85	92		98	91
Equals				316	256		239	254
Subtract			420	308		465	346
Add				448	322		493	344
Normalize			281	263		272	271
ToString			147	152		173	170

[September 19, 2005]

From C++ to ActionScript 2.0, 4: Abstract Classes

Filed under: ActionScript — @ 11:13 am

From Canadian Mind Products’ Java Glossary:

abstract class
A class that is missing definitions for one or more methods. You can’t thus create an object of that class. You must first create a subclass and provide definitions for the abstract methods. Unlike interfaces, abstract classes may implement some of the methods. Though you can’t instantiate an abstract class, you can invoke its static methods.

For a comparison of abstract classes and interfaces, and what they’re good for, have a look at Java Glossary: Interface vs Abstract Class, just about all of which (except for the stuff about "static final constants") is applicable to ActionScript as well, once we implement abstract classes.

ActionScript 2.0 doesn’t natively support abstract classes, although the abstract keyword is reserved for future use. I’ve seen people simulate abstract methods by throwing an error in each one to complain at run time that it hasn’t been overriden, but there’s a better way. We can make a real abstract class that implements none, some, or all of an interface’s methods, and enforces that subclasses implement any missing methods.

In C++, abstract methods are called pure virtual functions, and they’re specified in the class definition by setting the function body to zero (actually the null pointer):

virtual void someFunction() = 0;

In C++ the presence of a pure virtual function makes the class uninstantiable, and specifies that the function must be implemented in any instantiable subclass. (As for regular virtual functions, if you’re not familiar with C++, let’s just say that all methods in AS2 are virtual anyway.)

A pure virtual function can actually have an implementation in C++ — you define it outside the class body — without relieving the derived class of the obligation to override it. We can accomplish this in ActionScript too.

But why?

This could be useful in the Window example. Window could insist that its derived classes provide their own version of clear but could still provide basic clear functionality common to all window types. For example, Window::clear might position the cursor to the middle of the screen when invoked as the final action of a derived class clear member function.
(James Coplien, Advanced C++, p. 133)

Maybe not the coolest example, but you get the idea.

But how?

It looks like a real problem: In ActionScript we can only avoid implementing a method by omitting the method declaration from the class; there’s no way to specify it as abstract.

Suppose we create an interface IAbstract and a class Abstract. The class cannot declare that it implements IAbstract; otherwise it couldn’t omit any method definitions. But it must ensure that its subclasses supply any missing definitions of IAbstract’s methods. Andrew Simmons showed a neat solution to this in an article at ActionScript.org. I’ve taken his example and expanded on it somewhat so I can understand it better, and so I can show how to do an abstract method with a body in ActionScript.

If you’d like to see some demonstration tests, save the four files I’ll show here, open up Flash, make a FLA in the same directory, put Extending.demo(); in Frame 1’s Actions, and then Test Movie.

First I’ll create the interface:


interface IAbstract
{

function methodOne():Void;
function methodTwo():Void;
function methodThree():Void;
function methodFour():Void;

}

And now here’s the Abstract class:


class Abstract
{

private var sub:IAbstract;

private function Abstract()
{
	trace("\tcreating Abstract object");

	sub = IAbstract(this);

	// Flash Player 7 and above:
	if (sub == null)
	{
		throw new Error("Classes extending Abstract must implement IAbstract");
	}
	// end Flash Player 7 and above

	/* for Flash Player 6 use this runtime type check
	if (!(sub instanceof IAbstract))
	{
		sub = null;
		trace("Classes extending Abstract must implement IAbstract");
	}

	*/

	else
	{
		trace("\tAbstract object created without error");
	}
}

public function toString():String
{
	return "Abstract instance";
}

function methodTwo():Void
{
	trace("\t\tmethodTwo defined in Abstract");
}

function methodFour():Void
{
	trace("\tmethodFour defined in Abstract invokes:");
	sub.methodOne();
	methodTwo();
	sub.methodThree();
}

// Use a static initializer to install an implementation for methodThree:
private static var init:Boolean = initMethodThree();
private static function initMethodThree():Boolean
{
	Abstract.prototype.methodThree = function()
		{
			trace("\t\tmethodThree added to Abstract, executed by " + this);
		};
	return true;
}

}

We’ve defined methodFour to call methodOne, methodTwo, and methodThree, though we haven’t defined methodOne, and at compile time we haven’t yet defined methodThree. To quote Simmons:

Here is the foundation of this example. We store a reference to the current instance of the class extending Abstract in the variable sub.

sub = IAbstract(this);

This allows us to invoke the abstract method [methodOne or methodThree] without a compiler error. If the extending class does not extend the interface, the cast returns null and we throw an exception. This forces the extending class to implement the interface IAbstract. The result is an uninstantiable class which contains some implementation and enforces specific and complete implementation by all deriving classes.

Then we use a static initializer to install an implementation for methodThree. methodThree will be our pure virtual with a body. Unlike in C++, the only way we could implement Abstract’s methodThree at compile time would be to define it in the class body, but then it would no longer be abstract, and a derived class could get away without overriding it. So we do the next best thing, and define it as soon as possible at run time. And we have to do it the old-fashioned way, using the class prototype.

Before I show the Extending class, I want to throw in a little class we’ll use for testing — Phony, which extends Abstract but does not implement IAbstract:


class Phony extends Abstract
{

public function Phony()
{
	trace("\nPhony object created");
}

}

Now the Extending class is going to override Abstract’s default implementation of methodTwo and Abstract’s pure virtual function methodThree. Each of these two functions in Extending will do its own thing and then invoke Abstract’s version.

Abstract’s version of methodThree doesn’t exist until run time. This has two consequences for Extending: as we’ve said, if Extending didn’t override it, it couldn’t claim to implement IAbstract; in addition, Extending can only invoke it through the class prototype.

OK. Here’s Extending. It’s a little long because it contains all the tests:


class Extending extends Abstract implements IAbstract
{

function Extending()
{
	trace("\tExtending object created");
}

public function toString():String
{
	return "Extending object";
}

function methodOne():Void
{
	trace("\tmethodOne defined in Extending");

}

public function methodTwo():Void
{
	trace("\tmethodTwo defined in Extending");

	trace("\t\tinvoking Abstract’s methodTwo:");
//	Invoke the Abstract class’ methodTwo on this object:
	super.methodTwo();
}

public function methodThree():Void
{
	trace("\tmethodThree defined in Extending");

	trace("\t\tinvoking Abstract’s methodThree:");
//	Invoke the Abstract class’ methodThree on this object
//	We can’t say super.methodThree(); because it’s not defined at compile time:

	Abstract.prototype.methodThree.apply(this, arguments);
}

public static function demo()
{
	trace("");
	trace("Test 1: create an Abstract.");
	trace("This test wouldn’t even compile if we were not running it");
	trace("from within a class that extends Abstract,")
	trace("since Abstract’s constructor is private:");
	trace("");

	var ab:Abstract;
	try
	{
		ab = new Abstract();
	}
	catch (e:Error)
	{
		trace("\tError: " + e);
	}

	trace("\tThe Abstract is " + ab);
	trace("");
	trace("We just can’t instantiate an Abstract.");
	trace("Good.  Abstract is an abstract class.");

	trace("");
	trace("Test 2: create an Extending, which extends Abstract and,");
	trace("with the help of methodFour inherited from its parent");
	trace("Abstract, implements IAbstract:");
	trace("");

	var theExt:Extending;

	try
	{
		theExt = new Extending();
	}
	catch (e:Error)
	{
		trace("\tError: " + e);
		return;
	}

	trace("");
	trace("Test 3: create a Phony, which extends Abstract");
	trace("but does not implement IAbstract:");
	trace("");

	var thePhony:Phony;

	try
	{
		thePhony = new Phony();
	}
	catch (e:Error)
	{
		trace("\tError: " + e);
	}

	trace("");
	trace("OK, so Abstract demands that its subclasses implement IAbstract");
	trace("in order to be instantiated.");

	trace("");

	trace("Does class Abstract directly implement methodOne?  " +
			(Abstract.prototype.hasOwnProperty("methodOne") ? "yes" : "no"));

	trace("Does class Abstract directly implement methodTwo?  " +
			(Abstract.prototype.hasOwnProperty("methodTwo") ? "yes" : "no"));

	trace("Does class Abstract directly implement methodThree?  " +
			(Abstract.prototype.hasOwnProperty("methodThree") ? "yes" : "no"));

	trace("");
	trace("Does class Extending directly implement methodOne?  " +
			(Extending.prototype.hasOwnProperty("methodOne") ? "yes" : "no"));

	trace("Does class Extending directly implement methodTwo?  " +
			(Extending.prototype.hasOwnProperty("methodTwo") ? "yes" : "no"));

	trace("Does class Extending directly implement methodThree?  " +
			(Extending.prototype.hasOwnProperty("methodThree") ? "yes" : "no"));

	trace("");
	trace("Test 4: let the Extending object execute methodFour:");
	trace("");
	theExt.methodFour();

	trace("");
	trace("Extending’s methodThree, after tracing out its own message,");
	trace("successfully invokes Abstract’s methodThree.");
	trace("methodThree in Abstract is indeed a pure virtual function");
	trace("in that it must be overridden in a derived class if the derived");
	trace("class is to have any instances, but it can be given a body, with the cost");
	trace("that it must be both defined and called with slightly indirect syntax.");
}

}

[September 12, 2005]

I Gotcha ToolTip Right Heah

Filed under: ActionScript — @ 4:01 pm

I was glad to see the latest in Lee Brimelow’s great series of video Flash tutorials, this one on making tooltips in Flash. I’d been meaning to implement tooltips in my current project for a while, and this got me going. But you know I always prefer to do it in code that I can reuse anywhere, and not depend on library assets if I can help it. And tooltip is a prime candidate for implementation with a Singleton class, so…


import ascb.util.Proxy;

class com.nodename.utils.ToolTip
{
static var version:String = "$Id: ToolTip.as,v 1.9 2006/03/26 18:14:15 ashaw Exp $";

public static function setBoxSize(wd:Number, ht:Number):Void
{
	ToolTip._boxWidth = wd;
	ToolTip._boxHeight = ht;
}

public static function setPointSize(ps:Number):Void
{
	ToolTip._pointSize = ps;
}

/*
 *
 * @function	create
 * @param	tipTarget	the object over which the tooltip is to be shown:
 * 				MovieClip, MCComposite, or Button
 * @param	tip		the text to be displayed in the ToolTip
 *
 */
public static function create(tipTarget, tip:String):Void
{
	tipTarget.onRollOver = Proxy.create(ToolTip, ToolTip.showToolTip, tip);
	tipTarget.onRollOut = tipTarget.onDragOut = ToolTip.hideToolTip;
}

/////////////////////////////////////////
//                                     //
//       End of public interface       //
//                                     //
/////////////////////////////////////////

private static var theOnlyToolTip:ToolTip;

private static function showToolTip(tip:String):Void
{
	if (theOnlyToolTip == undefined)
	{
		theOnlyToolTip = new ToolTip();
	}
	theOnlyToolTip.p_showToolTip(tip);
}

private static function hideToolTip():Void
{
	if (theOnlyToolTip == undefined)
	{
		theOnlyToolTip = new ToolTip();
	}
	theOnlyToolTip.p_hideToolTip();
}

private var _mc:MovieClip;
private var _tf:TextField;
private var _tFormat:TextFormat;

private static var _boxWidth:Number = 150;
private static var _boxHeight:Number = 50;

private static var _pointSize:Number = 10;

private var _tailLeft:Number;		// distance from left edge of box to left edge of tail
private var _tailWidth:Number;
private var _tailHeight:Number;
private var _tailTipOffset:Number;	// tip of tail is this far above registration point
private var _textSideMargin:Number;

private var topY:Number;
private var leftX:Number;

private function ToolTip()
{
	_tailLeft = .40 * ToolTip._boxHeight;
	_tailWidth = .40 * ToolTip._boxHeight;
	_tailHeight = .60 * ToolTip._boxHeight;
	_tailTipOffset = 5;
	_textSideMargin = 5;

	var trueParent:MovieClip = _level0;	// it has to live somewhere...

	this._mc = trueParent.createEmptyMovieClip("nodenameToolTip", trueParent.getNextHighestDepth());

	this._mc._visible = false;

	leftX = -this._tailLeft;
	topY  = -ToolTip._boxHeight - this._tailHeight - this._tailTipOffset;

	this.draw(leftX, topY, ToolTip._boxWidth, ToolTip._boxHeight, this._tailWidth, this._tailTipOffset);

	this.makeTextField(leftX, topY, ToolTip._boxWidth, ToolTip._boxHeight);
}

private function draw(leftX:Number, topY:Number, boxWidth:Number, boxHeight:Number,
			tailWidth:Number, tailTipOffset:Number):Void
{
// TODO:	make rounded corners

	var rightX:Number	= leftX + boxWidth;
	var bottomY:Number	= topY + boxHeight;

	with (this._mc)
	{
		moveTo(leftX, topY);
		lineStyle(1, 0x000000, 100); // black
		beginGradientFill(
				"linear",
				[0xfff3d9, 0xfffbf2],	// colors
				[100, 100],		// alphas
				[0, 255],		// ratios
				{
					matrixType:"box",
					x: leftX, y: topY,
					w: boxWidth, h: boxHeight,
					r: Math.PI/2	// 90 degrees: vertical gradient
				}
				);
		lineTo(rightX, topY);
		lineTo(rightX, bottomY);
		lineTo(tailWidth, bottomY);
		lineTo(0, -tailTipOffset);	// tip of tail
		lineTo(0, bottomY);
		lineTo(leftX, bottomY);
		lineTo(leftX, topY);
		endFill();
	}
}

private function makeTextField(leftX:Number, topY:Number, boxWidth:Number, boxHeight:Number):Void
{
	this._mc.createTextField("_tf", this._mc.getNextHighestDepth(),
				leftX, topY, boxWidth, boxHeight);
	this._tf = this._mc._tf;
	this._tFormat = new TextFormat("_sans", ToolTip._pointSize, 0×000000);
	this._tFormat.align = "center";
	this._tFormat.leftMargin = this._tFormat.rightMargin = this._textSideMargin;
	this._tf.setNewTextFormat(this._tFormat);
	this._tf.wordWrap = true;
	this._tf.autoSize = "none";
}

private var _toolTipIntervalID:Number;
private var _toolTipDelay:Number = 500; // milliseconds

private function p_showToolTip(tip:String):Void
{
	this._toolTipIntervalID = setInterval(this, "reallyShowToolTip", this._toolTipDelay, tip);
}

private function reallyShowToolTip(tip:String):Void
{
	clearInterval(this._toolTipIntervalID);

	this._tf.text = tip;
	updateToolTip();
	this._mc._visible = true;

	this._mc.onMouseMove = Proxy.create(this, updateToolTip);
}

private function updateToolTip():Void
{
	setFlip();

	this._mc._x = this._mc._parent._xmouse;
	this._mc._y = this._mc._parent._ymouse;

	updateAfterEvent();
}

public function setFlip():Void
{
	var loc:Object = {x: this._mc._xmouse, y: this._mc._ymouse};
	this._mc.localToGlobal(loc);
	if (loc.y + topY < 5)
	{
		flipY();
	}
	else
	{
		unflipY();
	}
	if (loc.x + leftX + ToolTip._boxWidth > Stage.width - 5)
	{
		flipX();
	}
	else
	{
		unflipX();
	}
}

private function flipY():Void
{
	this._mc._yscale = -100;
	this._tf._yscale = -100;
	this._tf._y = topY + ToolTip._boxHeight;
}

private function unflipY():Void
{
	this._mc._yscale = 100;
	this._tf._yscale = 100;
	this._tf._y = topY;
}

private function flipX():Void
{
	this._mc._xscale = -100;
	this._tf._xscale = -100;
	this._tf._x = leftX + ToolTip._boxWidth;
}

private function unflipX():Void
{
	this._mc._xscale = 100;
	this._tf._xscale = 100;
	this._tf._x = leftX;
}

private function p_hideToolTip():Void
{
	clearInterval(this._toolTipIntervalID);
	this._mc._visible = false;
	delete this._mc.onMouseMove;
}

}

Contents copyright © Alan Shaw 2005-2008

25 queries. 0.289 seconds. Powered by WordPress version 2.5.1