AlecMcE.com

Coding on the Flash Platform

Archive for the ‘library’ Category

Invalidation Manager

View Comments

Introduction

Invalidation is a pattern employed to solve problems where updating a model’s data may happen multiple times per frame, but updating the view’s rendering should happen only once. When dependencies exist between multiple models this can get complicated and potentially messy. This article looks at a solution to this mess by unifying invalidation logic into a single manager class.

The purpose of this discussion is not to offer a new class for people to take and use in their libraries so much, though you are welcome, if you should want to. Rather it is to discus a problem and my solution. Hopefully some people will learn something from reading it, and hopefully I will learn something by getting feedback about it!

The Invalidation Pattern

Consider the following simple implementation for drawing a coloured circle:

public class Circle extends Sprite
{

	private var _color:uint;
	private var _radius:Number;

	public function get color():Number { return _color; }
	public function set color(value:Number):void
	{
		_color = value;
		resolve();
	}

	public function get radius():Number { return _radius; }
	public function set radius(value:Number):void
	{
		_radius = value;
		resolve();
	}

	private function resolve():void
	{
		graphics.clear();
		graphics.beginFill(_color);
		graphics.drawCircle(0, 0, _radius);
		graphics.endFill();
	}

}

The problem here is that if in one frame I changed both the radius and the color, the resolve() function would be called twice. Though not a problem in this case, it could be if the resolve() function involved a lot of complex logic.

The invalidation pattern resolves it this way:

public class Circle extends Sprite
{

	private var _color:uint;
	private var _radius:Number;

	public function get color():Number { return _color; }
	public function set color(value:Number):void
	{
		_color = value;
		invalidate();
	}

	public function get radius():Number { return _radius; }
	public function set radius(value:Number):void
	{
		_radius = value;
		invalidate();
	}

	public function invalidate():void
	{
		... at some future point of my choosing, trigger resolve() ...
	} 

	public function resolve():void
	{
		graphics.clear();
		graphics.beginFill(_color);
		graphics.drawCircle(0, 0, _radius);
		graphics.endFill();
	}

}

Often in AS3, the invalidate() function looks like this:

public function invalidate():void
{
	addEventListener(Event.ENTER_FRAME, onEnterFrame);
}

private function onEnterFrame(event:Event):void
{
	removeEventListener(Event.ENTER_FRAME, onEnterFrame);
	resolve();
}

It is a good pattern that solves the problem of multiple resolve() calls. The actual mechanism contained inside invalidate() which causes resolve() to be called once at a convenient point is unimportant. For this sort of structure, this simple implementation does the job.

Where The Invalidation Pattern Becomes Complicated

Imagine that you have two classes: a line, and a circle, the intersection of which defines two circular segments. In fact, don’t imagine: look at this:

The Flash plugin is required to view this object.

Both the line and circle implementations consist of two points which, on moving, invalidate the their equations. On resolve() the equations are resolved. The segments depend upon the the line and circle; when the line or circle is invalidated, they should be invalidated also.

A mechanism for the segments to know when their definiens (that which defines them) are resolved is required, suggesting that each element conforms to a common interface:

public interface Invalidates
{

	function invalidate():void;

	function get invalidated():Signal;

	function resolve():void;

}

and an implementation along these lines:

public class Line implements Invalidates
{

	private var _ax:Number;
	private var _ay:Number;
	private var _bx:Number;
	private var _by:Number;

	private var _invalidated:Signal;

	public function Line()
	{
		_invalidated = new Signal(Invalidates);
	}

	public function get ax():Number { return _ax; }
	public function set ax(value:Number):void
	{
		_ax = value;
		invalidate();
	}

	... missing a few uninteresting methods ...

	public function invalidate():void
	{
		_invalidated.dispatch(this);
	}

	public function resolve():void
	{
		... resolve the line ...
	}

}

public class Segment implements Invalidates
{
	private var _line:Line;
	private var _circle:Circle;

	... missing the constructor ...

	public function get line():Line { return _line; }
	public function set line(value:Line):void
	{
		_line = line;
		_line.invalidated.add(onInvalidated);
	}

	... missing a few uninteresting methods ...

	private function onInvalidated(definien:Invalidates):void
	{
		invalidate();
	}

	public function resolve():void
	{
		_line.resolve();
		_circle.resolve();

		... the rest of the resolution ...
	}
}

The problem here is the same problem that led us to consider the Invalidation Pattern in the first place: namely, that the Line’s resolve() method is going to get called a lot. Sure, we can pack the methods with lots if calls like:

private function invalidate():void
{
	if (_isInvalidated)
		return;

	_isInvalidated = true;
	invalidate();
}

public function resolve():void
{
	if (!_isInvalidated)
		return;

	_isInvaldiated = false;
	_line.resolve();
	_circle.resolve();

	... the rest of the resolution ...
}

However, it starts to feel like the classes are consumed with handling the invalidation management, rather than with what should be their single responsibility of properly modelling the geometrical entities for which they are named.

An important feature of this implemenation is that it guarantees that the Line’s and Circle’s resolve() methods are called before the sgement resolves. If B depends on A, then A must resolve before B resolves. This is a key feature of the Invalidation Manager, below.

Managing Invalidation

The current as3geometry library attempts to solve the problem of invalidation using the InvalidationManager.as class.

The Invalidation Manager handles classes that satisfy the Invalidates.as interface. Invalidates objects can be registered to the InvalidationManager, and dependencies can be established. As this happens, objects are categorized into tiers.

Initially, since every object is independent of each other when registered, newly registered elements are considered in tier 0. However, if a dependency is established such that B depends on A, B is put into the tier above A, so if A is in tier 0, B is in tier 1. If then C depends on B, since B is already in tier 1, C goes into tier 2.

The utility of the tiers comes from the fact that all objects in tier 0 are independent of each other and may be resolved in any order. Tiers are resolve in ascending order, ensuring that any object that is depended upon is resolved before any object which depends on it.

The problem with this approach comes when, for example, A depends on B depends on C already so they have the following tiers:

Then, if I have another object D already in tier 3, and define that B depends on D, I must move B to tier 4 and therefore also move C to tier 5.

Circularity presents an interesting problem within this system. If A depends on B depends on C, and then I define that C depends on A, the tier system breaks down. Currently attempting to create a circular reference will fail and an error will be thrown.

In order to keep the InvalidationManager clean, it does not actually handle when it resolves, but instead dispatches a signal when it has elements that require resolution. In the as3geometry implementation the AS3GeometryContext.as wraps the InvalidationManager and hooks it to the player events to so that when invalidation occurs it is resolved in good time.

The Invalidates classes, other than telling the contxt what it has a relationship to and what it does not, no longer contains any code that tries to manage other objects’ invalidations:

public class Line implements Invalidates
{

	private var _ax:Number;
	private var _ay:Number;
	private var _bx:Number;
	private var _by:Number;

	private var _context:InvalidationManagerContext;
	private var _invalidated:Signal;

	public function Line(context:InvalidationManagerContext)
	{
		_invalidated = new Signal(Invalidates);

		// elements that don't depend on other classes  like this don't really need the
		// context stored, but I store it here for consistency, and in case the structure
		// changes
		_context = context;
		context.register(this);
	}

	public function get ax():Number { return _ax; }
	public function set ax(value:Number):void
	{
		_ax = value;
		invalidate();
	}

	... missing a few uninteresting methods ...

	public function invalidate():void
	{
		_invalidated.dispatch(this);
	}

	public function resolve():void
	{
		... resolve the line ...
	}

}

public class Segment implements Invalidates
{
	private var _line:Line;
	private var _circle:Circle;

	private var _context:InvalidationManagerContext;
	private var _invalidated:Signal;

	public function Segment(context:InvalidationManagerContext)
	{
		_invalidated = new Signal(Invalidates);

		_context = context;
		_context.register(this);
	}

	public function get line():Line { return _line; }
	public function set line(value:Line):void
	{
		_line = line;
		_context.addDependency(_line, this);
	}

	... missing a few uninteresting methods ...

	private function onInvalidated(definien:Invalidates):void
	{
		invalidate();
	}

	public function resolve():void
	{
		... just resolve here! this is the big win ...
	}
}

Conclusion

A benefit to this approach is that Invalidates classes need very little boiler-plate code. The author of an Invalidates class need know only that changes should call invalidate() and that all resolution logic happens in resolve(). Responsibilities are extremely well separated.

The as3geometry implementation wraps the InvalidationManager into an AS3GeometryContext.as class that has a little more logic than the InvalidationManager, which does not have a mechanism for actually triggering the mass resolution. While the InvalidationManager handles the internal logic set-out above, the AS3GeometryContext handles hooking up the InvalidationManager to the player events.

There is a downside, of course. Currently in as3geometry, the constructor of every class contains a reference to the AS3GeometryContext, and every class stores a reference to it. It is not clear to me that this approach is avoidable, unless the manager is defined as a singleton. There are lots of good reasons why singletons are bad, not least because I might have good reason to have two parallel geometric contexts in one application.

Written by alec

August 11th, 2010 at 11:45 pm

Parabolas and Quadratic Bezier Curves

View Comments

As a follow-up to my as3geometry update on parabolas and quadratic bezier curves, I wanted to record the mathematics used to make the translation from parabola to quadratic bezier curve. Hoping to avoid the work of thinking about this for myself, I went online to understand the relationship, but could not find the calculation explained anywhere. The answer presented itself once I started to draw this diagram in Illustrator. It is amazing how a well-drawn, careful diagram can give you the insights you need!

The parabola is defined by the directrix AB and the focus C

It was apparent from the diagram that:

  • H is the midpoint of the line segment AC;
  • AD is perpendicular to AB, and HD is perpendicular to AC, so D can be calculated as the intersection of these lines;
  • J is the midpoint of the line segment BC;
  • BE is perpendicular to AB and JE is perpendicular to BC, so E can be calculated as the intersection of these lines;
  • K is the intersection of the lines JE and HD, so E can be calculated as the intersection of these lines;
  • The parabolic segment defined by the line segment directrix AB and the focus C is also defined by a quadratic bezier curve with the start point D, the end point E and the control point K.

The full calculation (with some different variable names I’m afraid!) was codified in the ParabolaDrawer.as class.


Written by alec

August 10th, 2010 at 8:00 am

Posted in as3,library,math

as3geometry – Parabolas

View Comments

The as3geometry library has been neglected of late, but I have managed to push out a couple of important updates this week: the invalidation manager and parabola handling. I will discuss the invalidation manager later in the week since it is quite a technical and complex update. The implementation of parabolas is rather more straightforward.

Parabola defined by a line and vertex

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

Parabola segment defined by a line segment and vertex:

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

Usefully, a parabola (which, precisely, is “the locus of points equidistant from a point and a line”) can be expressed as a quadratic bezier curve. The implementation ‘simply’ finds the three control points of a quadratic bezier curve and then uses the Flash Player’s Graphics.curveTo method to draw.

Written by alec

August 9th, 2010 at 8:00 am

Posted in as3,library,math

[Embed(source="asset.swf")] Gotcha (and Workaround)

View Comments

It’s been a long-time without a post. Part of the reason for that has been starting work at a new job (which involves a commute), and part has been that as part of my new job, I was actually encouraged to spend time playing World of Warcraft. To me, that’s like taking a crowbar to Pandora’s Box and having a peek inside. I learned quite a lot, but have also played just a few too many Warsong Gulches.

Back to work, and I was doing a little prototyping this evening, when I came across a familiar problem: In AS3 we can use

[Embed(source="asset.swf", symbol="symbol")]
private var symbolClass:Class;

var symbol:MovieClip = new symbolClass();

to embed a symbol from an art SWF in what is probably a code-built SWF. That’s great, but what if you want to embed an entire SWF?

[Embed(source="asset.swf")]
private var assetClass:Class;

var asset:MovieClip = new assetClass();

looks like it should do the trick, but you can’t access any of the information within the asset. That’s a real pain, the reason for which is pretty convoluted. I remembered working around this problem in the past, and happily managed to unearth a long-forgotten treasure in my codebase, which I thought I’d share (having rapidly refactored it to use as3-signals, naturally).

package com.alecmce.util
{
	import org.osflash.signals.Signal;

	import mx.core.MovieClipAsset;

	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.MovieClip;
	import flash.events.Event;

	public class UnpackEmbed
	{
		private var _ready:Signal;

		private var _asset:MovieClipAsset;
		private var _content:MovieClip;

		public function UnpackEmbed(assetClass:Class)
		{
			_asset = new assetClass();
			_ready = new Signal(UnpackEmbed);

			var loader:Loader = Loader(_asset.getChildAt(0));
			var info:LoaderInfo = loader.contentLoaderInfo;
			info.addEventListener(Event.COMPLETE, onLoadComplete);
		}

		private function onLoadComplete(event:Event):void
		{
			var info:LoaderInfo = LoaderInfo(event.target);
			info.removeEventListener(Event.COMPLETE, onLoadComplete);

			_content = MovieClip(info.loader.content);
			_ready.dispatch(this);
		}

		public function get content():MovieClip
		{
			return _content;
		}

		public function get ready():Signal
		{
			return _ready;
		}

		public function get asset():MovieClipAsset
		{
			return _asset;
		}
	}
}

When you embed a SWF in this way then instantiate it, Flash somehow conspires to create a MovieClipAsset with a Loader inside, which will be ‘loading’ the already-embedded content. The content is not available immediately (it may be sometimes, I have encountered cases where it was not), so you have to wait for an Event.COMPLETE to be fired before you can access it. This class exposes a signal that informs you when the content is ready. It could probably be more rigorous, such as including an isComplete flag, but it serves my purposes, when used in the following manner:

[Embed(source="asset.swf")]
private var assetClass:Class;

asset = new UnpackEmbed(assetClass);
asset.ready.addOnce(onAssetReady);

private function onAssetReady(asset:UnpackEmbed):void
{
	// now we can access the asset.content!
}

Written by alec

May 21st, 2010 at 12:02 am

Posted in as3,gotcha,library

RobotLegsPong Part 1 – Overview

View Comments

Introduction

RobotLegsPong attempts to combine RobotLegs application framework with the as3signals event model and a Frame-Ticker (as well as a pinch of ASUnit unit-testing) to create a ‘game’.

It is an open-source project, available on GitHub as an example of using RobotLegs together with these other technologies.

Source Code: github/alecmce/RobotLegsPong

RobotLegsPong is not a very good game; it is playable only in the loosest sense. Bats require to be dragged in order to move (and there is no computer-logic to play against), so you could not really play against an opponent. The purpose of the exercise was to see what structure a game like Pong would take within the RobotLegs framework.

The purpose of this article is to describe the particulars of the technologies and why I chose them. In a follow-up article, I will look in more detail how RobotLegsPong is wired up.

Why I Chose These Technologies

RobotLegs

The RobotLegs framework is a lightweight, flexible application framework that allows coders to spend less time wiring up their application, and more time working on its functionality.

Frameworks are often criticised because they force developers to write code in a particular way, which might be inappropriate for a particular context. I have used Cairngorm, Pure MVC, as well as some proprietary frameworks, and have found that all of them do some things well, and do other things clumsily. So far I have not found this problem with RobotLegs, because most of the time it gets out of the way and lets you code; in fact, it does more than that: it reduces the amount of code you have to write.

as3signals

RobotLegs comes with an application event bus, but I have decided to circumvent that and use as3signals instead. as3signals allows you to define an object which acts as an event dispatcher within an application.

I like as3signals because describing an event as a Signal object allows my event model to be described through interfaces. as3signals is supposed to be fast too. Then again, interfaces are slow. The relative speeds of the approaches is interesting, but not a significant factor in my decision making process.

Frame-Ticker

The frame-ticker is a simple idea: rather than creating multiple enter-frame event listeners, each creating event objects, create one enter-frame listener and a main game loop iterating through those methods that need to be called per-iteration.

This implementation came out of a discussion on Twitter that I was involved in on the fringes; I wrote about it in a previous blog post.

ASUnit

Unit testing enables developers to test portions of their code in isolation, so that they can be confident that every method works exactly as the developer intended. They also represent a secondary form of documentation-in-code; if you want to know how I meant a method to work, reading the unit test will tell you almost everything you need to know (including, if a good test is missing, what I didn’t consider).

That said, I have not unit tested every method; in this example I have only unit-tested the geometric logic for the bats. This is bad practice, however under time constraints I think that it is most important to unit test what is complex, mathematical or ambiguous, since this is where behaviour is most likely to fall down.

[Inject]

The ‘trick’ to using RobotLegs is that you do not need to inject dependent objects into classes when you contruct them; this is done automatically. This code:

public class Dependee {}

public class MyExample
{
	public var dependee:Dependee;

	public function MyExample(dependee:Dependee)
	{
		this.dependee = dependee;
	}
}

becomes this code:

public class Dependee {}

public class MyExample
{
	[Inject]
	public var dependee:Dependee;
}

The metadata tag [Inject] has the effect of automatically injecting the dependee into the class when it is constructed. This isn’t much on the face of it, but it is enough. It feels to me as though a quarter of my code is actually just wiring up classes so that they each have access to the classes that they need to function, or passing objects around methods. The [Inject] tag lets me shift all of that wiring into the RobotLegs framework.

Context

There is a little more to the RobotLegs story however; how should RobotLegs react when it comes across an [Inject] tag? It could construct a new Dependee object, or it could be that I want only one Dependee object to be injected wherever the [Inject] tag is found. This is where the Context class becomes meaningful.

In the PongContext, different classes are wired up in different ways. Their definitions are defined by adding injector mappings and the mediatorMap mappings in the startup method.

public class PongContext extends Context
{
	override public function startup():void
	{
		...
		injector.mapSingleton(DragMechanism);
		injector.mapSingletonOf(Geometry, GeometryModel);
		injector.mapClass(BatGeometry, BatGeometryModel);
		...
	}
}

injector.mapSingleton

injector.mapSingleton(DragMechanism)

mapSingleton indicates that only one instance of the class entered should be constructed. In this case, just one DragMechanism will be created, and injected whenever this is found:

[Inject]
public var mechanism:DragMechanism;

injector.mapSingletonOf

injector.mapSingletonOf(Geometry, GeometryModel);

mapSingletonOf extends the mapSingleton idea to interfaces and their implementing classes. In this case the Geometry interface is mapped to the GeometryModel class. The injection is called as follows:

[Inject]
public var model:Geometry;

No reference to the GeometryModel is needed in any class other than the PongContext. This allows a very pure use of interfaces across the application.

injector.mapClass

injector.mapClass(BatGeometry, BatGeometryModel)

injector.mapClass works a little differently; whenever a BatGeometry interface is flagged to be injected, a new instance of BatGeometryModel is constructed and injected into the class, so the following code is used to create the injection:

Each BatMediator needs its own BatGeometry object. By using the mapClass method, multiple BatGeometryModel objects will be constructed, one for each time the following code is found:

[Inject]
public var model:BatGeometry;

Mediators and Components

public class PongContext extends Context
{
	override public function startup():void
	{
		...
		mediatorMap.mapView(Background, BackgroundMediator);

		addChild(new Background());
	}
}

mediatorMap references a slightly different aspect of the application: your view objects. The mediatorMap allows users to define associations between display objects and Mediators. These two parts comprise the View aspect of the standard MVC pattern.

The DisplayObject – in this case Background – is the asset that you want to be visible on the stage. In my pong example this is essentially just a Sprite, though it might just as well have been imported as a graphical asset in a SWC.

The Mediator is associated with the DisplayObject so that when an instance of the DisplayObject is created, the corresponding Mediator is also constructed. The BackgroundMediator looks something like this:

public class BackgroundMediator extends Mediator
{
	[Inject]
	public var view:Background;

	override public function onRegister():void
	{
		...
	}
}

The DisplayObject Background is injected into the BackgroundMediator when it is constructed. The DisplayObject can then be manipulated. It is an elegant solution that separates display object from UI logic.

addChild(new Background());

is all that is needed to wire the entire DisplayObject/Mediator combination.

RobotLegs & Tickers

The RobotLegs structure is particularly useful for wiring up a Ticker-based application because one ticker can be mapped as a singleton, and injected to all those methods that need to iterate every frame:

public class PongContext extends Context
{
	override public function startup():void
	{
		...
		injector.mapSingletonOf(Ticker, EnterFrameTicker);
		...
	}
}

RobotLegs with as3signals

I really like the style of event dispatching that as3signals provides, but my implementation demands that if one class requires another class then it must flag it to be injected, and then the signal wired up in a method flagged as [PostConstruct] (see GeometryModel). This doesn’t feel like the right solution.

Integrating as3signals with RobotLegs is beyond the scope of this article. It looks like Joel Hooks and Robert Penner are making inroads into this problem through as3signals’ Google Group. I look forward to seeing what they come up with.

Written by alec

January 19th, 2010 at 7:01 am

Circle Segments

View Comments

I’ve added circle segments to the as3geometry library. The two examples below demonstrate the definition of a segment by two vertices constrained to the circle radius and by a line and circle:

Circle Segment By Vertices

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

Circle Segment By Line And Circle

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

The library update also includes some preliminary work on the intersection of two polygons, but I haven’t yet sorted out the mutability yet, because it’s pretty complicated, and because I’m furiously busy this December preparing to move from the UK to the USA! Expect more udpates from late January onwards, once I’m settled in.

Written by alec

December 21st, 2009 at 1:01 pm

Posted in as3,library,math

as3geometry – line & circle intersection

View Comments

Addition to my as3geometry library, the intersection between a line and a circle

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

Written by alec

December 2nd, 2009 at 9:32 pm

Posted in as3,library,math

as3geometry – line & line intersection

View Comments

Addition to my as3geometry library, the Intersection between two lines (though in this case the lines are a ray and a segment!)

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

Written by alec

December 2nd, 2009 at 9:32 pm

Posted in as3,library,math

Tagged with

as3geometry Update

View Comments

I’ve been working on a GitHub geometry library recently.

The Flash plugin is required to view this object.


view source | click-and-drag the red points to interact

I’ve created various of these over the years, AS1, AS2, AS3, and now AS3+as3signals. It is gratifying to finally have the means to define events in interfaces!

The work uses the alpha of ASUnit version 4 (Rob Penner’s ‘Free Runner’ implementation, though it is not purely Test-Driven Development because in some cases I am refactoring old hitherto-private code. However, the classes in the test repository might serve as a useful example of the upcoming ASUnit library.

The work aims for elegance of structure rather than speed, and I will be concentrating on the Euclidean 2D for the time-being, though may look at extending it into 3D or non-Euclidean plane structures if I ever have the time.

http://github.com/alecmce/as3geometry

Next Steps

  • Circle-Line intersections
  • Polygon-Polygon intersections (Vertices)
  • Polygon-Polygon intersections (Polygons)
  • Circle-Circle intersection (Vertices)
  • Circle-Line intersection
  • Circle Sectors
  • Circle Rays
  • Circle-Circle intersection (Lens)
  • Circle-Circle intersection
  • Bezier Curves
  • Parabola

Written by alec

November 25th, 2009 at 11:03 pm

Posted in as3,library,math

Tagged with

Reflecting on as3signals

View Comments

Robert Penner has recently produced a new AS3 library called as3signals (which can be found on both Git Hub and Google Code. I have used it in one small ongoing project, and I am really impressed with it.

as3signals includes two methods for adding listeners: an add listener which permanently adds a listener until a remove listener is called, and an addOnce listener which is a one-time listener only. This is a really welcome addition to the event model. Current projects are littered with code like this:

objectThatDispatchesEvent.addEventListener(EventType, onEvent, [false, 0, true]);

private function onEvent(event:Event):void
{
	objectThatDispatchesEvent.removeEventListener(EventType, onEvent);
	...actual code for handling event...
}

Penner’s signals performs the task much more neatly:

signal.addOnce(onEvent);

private function onEvent(data:Data):void
{
	...actual code for handling event...
}

An interesting problem arises by having two methods for adding listeners that have different behaviours. What happens when you call first one, then the other, without calling a remove method in between? There are three main ways in which you could imagine the framework responding to this situation.

  1. Overwrite Model Each time a call redefines the relationship between signal and listener. In this model, subsequent calls overwrite the relationship;
  2. First-Wins Model The first call defines the relationship between signal and listener. In this model, subsequent calls are ignored. To change the relationship the current relationship should be first removed;
  3. Throw Error Model If you attempt to change the relationship between signal and listener without first calling remove then an error is thrown.

Overwrite Model

The add method defines a stronger relationship between signal and listener than the addOnce method. If I had previously called signal.addOnce(listener) and then called signal.add(listener), I am intentionally strengthening the signal-listener relationship, and want the relationship to change. Conversely, I would expect the relationship to weaken.

A weakness of this model is that if you were to inadvertently change the relationship between the signal and listener, this would be extremely difficult to debug.

First-Wins Model

If you assign an event listener with addEventListener in the as3 native event model then you assign a priority to the listener which defines the order in which listeners are called (an optional parameter, which is 0 by default). In the as3 native event model, you cannot change the listener priority with further addEventListener calls unless you first call removeEventListener. Subsequent listener priorities are ignored. Similarly then, we could take this approach for add and addOnce. All subsequent calls are ineffective unless remove is called.

A weakness of this model is that if you intentionally change the relationship between the signal and listener, then your change should be effective! The following code certainly looks like it should work, but in fact the second line does nothing at all!

signal.addOnce(listener);
signal.add(listener);

Throw Error Model

There are scenarios in which developers might purposefully use both add and addOnce methods without calling a remove method in between. However, there will likely be cases where the two methods are called interchangeably by mistake. This may not be easy to debug, and we could easily help the developer out here by changing the functionality so that if signal.addOnce(listener) and signal.add(listener) are called in either order without an intervening signal.remove(listener), that an error is thrown.

In many ways, this is an extension of the ‘first-wins model’, because it preserves the functionality of that model. However, it recognises that the developer that intends to change behaviour might write something like:

signal.addOnce(listener);
signal.add(listener);

and handles it by throwing an error. The developer can then quickly change his code to either simply

signal.addOnce(listener);

or

signal.addOnce(listener);
signal.remove(listener);
signal.add(listener);

Summary

Penner argues that since the as3 native event model follows the ‘first-wins model’ with respect to priorities, this is the preferred model for handling addOnce and add calls. This argument is a little unsatisfactory, not least because if native as3 design decisions were in-themselves satisfactory, he wouldn’t have written as3signals in the first place!

My immediate preference was for the ‘overwrite model’, but I can see why people might adopt the ‘first-wins model’. I imagine that there is a relatively even split between the two camps. Therefore, I believe that throwing an error is the least confusing of all possible decisions. If an error is thrown, it is more difficult to write code that has unintended consequences. It forces the inclusion of remove methods, which makes code more readable. It is easy to explain and to document.

Hopefully it is obvious that I have enormous respect Robert Penner and the as3signals project. However, these small design decisions can have a large influence on workflow and they are important to get right. Penner has given the code an osflash package and he is clearly committed to writing this for the community. I first argued this point very briefly on one of his blog posts on as3signals, but it is an issue that deserved more serious attention. This sort of design decision would be well served by community discussion. If you have an opinion, please comment!

General Notes on Events

AS3 signals came out of a discussion on the problems that exist in the current event model. It is difficult to see Adobe deprecate this model any time soon, because their component framework, Cairngorm and the greater Flash/Flex community are now so dependent upon this model that it is unfeasible to imagine that they would abandon it.

The only way that the signals approach could be adopted en masse is probably if Adobe migrate to it in some future language ASx {x > 3}, or if some major libraries start to adopt it as their event model of choice.

In an earlier post of mine AS4 Thought Experiment, I attempted to think through my preferred API for a future language’s event model. The article was a response to community chatter about the event model, in part driven by Penner.

We share similar perspectives on how an event model ought to work. On reflection, I prefer Penner’s implementation with event.add(listener) and event.addOnce(listener) to my proposed event += listener, though I still stand by the argument that ideally, events should be demarcated from elements of an object through their own syntax; in my example by using #event... and by defining them in interface or class through public event drag or public event #drag. This, of course, is never going to happen!.

Written by alec

October 6th, 2009 at 8:59 am

Posted in as3,library,opinion

Tagged with