Archive for the ‘library’ Category
RobotLegsPong Part 1 – Overview
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.
Circle Segments
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
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.
as3geometry – line & circle intersection
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
as3geometry – line & line intersection
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
as3geometry Update
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
Reflecting on as3signals
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.
- Overwrite Model Each time a call redefines the relationship between signal and listener. In this model, subsequent calls overwrite the relationship;
- 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;
- Throw Error Model If you attempt to change the relationship between signal and listener without first calling
removethen 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!.
Windows, Eclipse and GitHub
Setting up for using GitHub on my mac was a breeze. Setting up at work on Windows was a pain. I think that these steps will satisfactorily setup Eclipse to work with GitHub.
- If you haven’t got a GitHub account, create one;
- Install eGit in Eclipse;
- EGit for Eclipse is at http://www.jgit.org/updates;
- I was unable to install EGit into the FDT ’standalone’ version of Eclipse. If you have this problem you will need to download and install the (superior) Eclipse Classic Version;
- Download and install msysgit from Google Code;
- Create your own ssh key using
$ ssh-keygen -C "your@email.com" -t rsa
- Personally I’d use the “Use Git Bash only” option;
- This will generate your key to (typically):
c:\Documents and Settings\[yourname]\.ssh
Change the folder from .ssh to ssh, otherwise EGit will look in the wrong place;
- In Eclipse, “Import…” a new project and choose “Git Repository”.
- Copy the Public Clone URL from the GitHub repository that you want to clone into the Location URI.
Voronoi Diagrams – as3voronoi v.0.2
I have just posted an update for the as3voronoi library, which I started writing for a pet project of mine, after I became annoyed by the lack of a good open-source library!.
Here is a Delaunay Triangulation, generated by this class:
The Flash plugin is required to view this object.
Here is an incomplete Voronoi Triangulation, generated by this class. It is a first pass because it does not yet handle the closure of the loci of points at the extremities of the point cluster:
A stable working version of the Voronoi tessellation will appear here soon.
“Clean Code” in AS3
In 2008, Uncle Bob (Robert C. Martin) proposed to the Agile Community that the Agile Manifesto should have a fifth tenet: “Craftsmanship over Execution”.
It is often held as obviously true that functionality is the most important element of code, since without functionality code is useless. So long as code works, readability and execution speed are valuable. Broadly, Martin argued that this is wrong: craftsmanship is more important than functionality; the professionalism with which we produce our code is the most valuable aspect of our work.
Most software development teams execute, but they don’t take care. We value execution, but we value craftsmanship more. Uncle Bob
It is clear to see why this is a difficult sell! At the moment I work at an agency, where in many cases the typical ‘agency mentality’ prevails: work is to be done as quickly as possible. However, all experienced coders know the consequences of trying to do too much too quickly: quick work leads to bugs, bugs lead to more work, dissatisfied clients, frustrated bosses, and – too intangibly to explain to most bosses – more work on future projects because of less code-reuse. Perhaps you know and accept all this, but it is worth repeating. And repeating, and repeating.
That said, too many of the Flash community seem not to know these truths. The Flash community is in great need of learning from the Agile elder statesmen. Most third party libraries value functionality above all and show little care for craftsmanship.
The history of Flash coding has been to strive for speed; to squeeze functionality out of the Flash Player like no-one had squeezed before. Many people have also created third-party libraries to help fellow coders shortcut to functionality and avoid reinventing a particular wheel. Unfortunately those libraries tend to be poorly written, buggy and unreadable. Even classes in the AS3 Core Library, which is run by some significant Flash notables, seem to have been rushed, no effort taken to clean them up for future developers to learn from or extend.
A Practical Example – The Delaunay Algorithm
Recently I wanted to do some work by creating a Delaunay Triangulation Diagram. I knew what it is, and roughly how the algorithm worked, but as would most people, I Googled it in the hope that someone had already created the algorithm in AS3 that I could use. I found this post on indiemaps.com. It’s a lovely post if you want to understand what the Delaunay Triangulation is or what it does, but the accompanying code is utterly horrible. I defy anyone to be able to read through this algorithm and understand how it works. Here’s a snippet:
public static function triangulate(pxyz:Array):Array {
var v:Array=new Array();
var nv = pxyz.length;
for (i=0; i < (nv*3); i++) {
v[i]=new ITriangle();
}
// the points must be sorted on the x dimension for the rest to work
pxyz.sortOn("x", Array.NUMERIC);
var complete:Array = null;
var edges:Array = null;
var nedge = 0;
var trimax, emax = 200;
var status = 0;
var inside:Boolean;
var xp, yp, x1, y1, x2, y2, x3, y3, xc, yc, r;
var xmin, xmax, ymin, ymax, xmid, ymid;
var dx, dy, dmax;
var ntri = 0;
/* Allocate memory for the completeness list, flag for each triangle */
trimax = 4*nv;
complete = new Array();
for (var ic=0; ic<trimax; ic++) complete[ic] = false;
/* Allocate memory for the edge list */
edges = new Array();
for (var ie=0; ie<emax; ie++) edges[ie] = new IEdge();
/*
Find the maximum and minimum vertex bounds.
This is to allow calculation of the bounding triangle
*/
xmin = pxyz[0].x;
ymin = pxyz[0].y;
xmax = xmin;
ymax = ymin;
for (var i=1;i<nv;i++)
{
if (pxyz[i].x < xmin) xmin = pxyz[i].x;
if (pxyz[i].x > xmax) xmax = pxyz[i].x;
if (pxyz[i].y < ymin) ymin = pxyz[i].y;
if (pxyz[i].y > ymax) ymax = pxyz[i].y;
}
dx = xmax - xmin;
dy = ymax - ymin;
dmax = (dx > dy) ? dx : dy;
xmid = (xmax + xmin) / 2.0;
ymid = (ymax + ymin) / 2.0;
The problem with this class is plain: it was ported. Clearly it was also ported from a functional language that had no pretence towards OOP. Such ports rely on a line-by-line transposition from one language to another and a bit of testing to check they work. Porting normally means that the porter doesn’t understand what is being ported. It is translation but rarely transliteration.
In the case of the Delaunay Algorithm, perhaps this is not much of a problem; there aren’t really many ways to extend this algoritm. In the case of a framework however, it ought to be unforgivable. If people can’t read the code they can’t improve it, amend it, extend it, adjust it. It makes them consumers and prevents community interaction. The code, the users and the original developer all suffer because of it over the long term.
Faced with this code, I decided to write an implementation of Delaunay’s algorithm myself. I have had this reaction to nearly every piece of library code I’ve read recently! Out of frustration, I have started writing unit testing frameworks, bulk loaders, tween engines, architectural frameworks, 3D engines, and game engines before realising that I don’t have enough time to all these libraries’ functionalities, and will have to live precariously by accepting that the library is closed to me because of its practical complexity and unreadability.
I must point out that I greatly admire and respect the coders who have successfully created the versions of these frameworks that I use (and would admire a great many others if only I knew your work better!), but I can’t help be frustrated at the effort I have to expend in order to read what they have done, particularly if I find a bug! It is the wasted effort that frustrates me: someone understood what they were doing when they wrote this the first time, so why didn’t they write it in such a way that I could understand too?
My own version of the Delaunay Algorithm is in Google Code as the first iteration of the as3voronoi library. It is a work in progress and by no means perfect. Uncle Bob would chastise many of my naming choices, ridiculous comments, and probably some of the design choices that I have made. However, I think that you can at least read this implementation, starting with the math.delaunay.Delaunay class and working out into the other classes as necessary. Here is a method:
public function addPoint(point:Point):void
{
if (!listOfPoints.add(point))
return;
if (listOfPoints.count == 3)
addInitialTriangle();
else
addPointToTriangulation(point);
listOfEdges.clearUnusedEdges();
}
This is not a direct comparision between two functionally equivalent pieces of code, but merely try to give the flavour of the approach.
Please Write More Beautiful Code
If you can’t explain it simply, you don’t understand it well enough. Albert Einstein
If you are a serious AS3 coder, writing serious code that you hope people will take on and use for serious projects, then please write beautiful code, not just functional code. Write code that explains itself, that reads like English, that uses both sides of peoples’ brains.
Please don’t dazzle the community with archane cleverness, complex methods, and huge classes.
Please don’t let your code rot, by adding functional change on functional change without reflecting on the general structure of your code and refactoring where practical.
If you come from an artistic background and aren’t steady on your coding feet, innovate and explore and hack around, but then sit down with a friendly developer and pair program your innovation into something clean, readable, and reusable.
And please fight when your bosses or fellow coders contend that it’s not worth it. If it isn’t worth it on that day (and it probably will be), it certainly will be in three months time.
Agile Software Development Resources
These two books from Uncle Bob are excellent resources for learning about writing more elegant code:
Clone Loaded Swf
To create a clone of a loaded swf:
/**
* clone a Loader by duplicating its loaded bytes; this can be performed only once
* the loader has completed loading, otherwise you'll run into problems!
*
* @param source The original loader, which remains unchanged
* @return A new Loader that is a clone of source
*/
function cloneLoader(source:Loader):Loader
{
var clone:Loader = new Loader();
clone.loadBytes(source.contentLoaderInfo.bytes);
return clone;
}
This post is as much a quick reminder for me as for everyone else, but it came up today and it’s the sort of thing I do rarely enough to forget exactly how. Since I try as far as humanly possible not to contain code and assets in the same swf (at least for big projects), this is useful because it avoids the need to do something like this:
/**
* clone a display object, only if it has an ActionScript linkage ID
* otherwise you'll end up with an empty display object of some
* description.
*
* @param source The source display object
* @return A new display object that is a clone of source
*/
function cloneClip(source:DisplayObject):DisplayObject
{
var classToCreate:Class = Object(source).constructor;
return new classToCreate();
}
which requires that the DisplayObject has an AS3 linkage ID, and therefore that code and assets are not separated.


