Organization Chart update
I've received a lot of demand for this component, and even though it is taking quite some time, it will eventually be re-released as open source.
The main workload remains in writing clean documentation and tutorials.
For now, please visit this url for more information.
Igindo is our relatively new startup company, in time this blog will disappear and be moved over there.
Alchemy optimizations
[edit]
Even though this tweak does improve performance, better results can be achieved using any of the tools a few lines below.
The reason I'm using this optimization (for now), is that I wish to keep the main code as pure AS3 as possible.
This leaves an attempt to have the communication between the main code and the optimized code as quick as possible.
In theory, it would be possible to use the ascommons-bytecode API for instance, create a class to read/write to alchemy based on OPCodes, and then inline the OPCodes at runtime in extisting functions, I'll have a go at that sometime soon.
Joa Ebert's apparat
Source: http://code.google.com/p/apparat/
Blog: http://blog.joa-ebert.com/
as3swf by Claus Wahlers
Source: http://github.com/claus/as3swf
Blog: http://wahlers.com.br/claus/blog/
Nicolas Cannasse's haxe
Home: http://haxe.org/
Blog: http://ncannasse.fr/blog/adobe_alchemy
be sure to check those out.
[/edit]
Original post
Allright, first off, I am still working on the Organization chart component, a lot even, but I've decided to stop updating the open source project hosted at google code.
Why?
Well, lately I've been focusing a LOT on getting it as fast as possible.
You may already know that I handle most data crunching methods through Alchemy, all these methods are written in C code, and the result is pretty fast.
Now I'm ok with building this component and distributing it with the compiled Alchemy swc, most people who want to use it don't actually need to compile that C code.
Yet my lastest work involves some exotic optimizations which also have implications on the AS3 code and its maintainability.
To explain, let me summarize from the start how Alchemy is brought into place.
1. AS3 - Alchemy bridge
Those of you who have worked with Alchemy, will know that making calls from AS3 to Alchemy bytecode is expensive.
Each time you call an Alchemy method, your function will be unboxed-boxed-etc... which is quite processor intensive.
Now in general that's ok, most times when you call Alchemy, you call just one method and have a return scope which you read out in AS3.
However, my initial problem was that I needed to make lots of calls to Alchemy.
Here's why:
My C code will take the full Organization chart data provider, its main work method is a recursive function, which loops over that data provider and calculates each x and y position, based on the state of each item in that data provider.
This means that, for example, I use AS3 code to update an item in that data provider (or many items at once), I need to be able to update my c code right away so that this fast layout function can be refreshed.
AS3 won't render the full data provider, instead Alchemy will indeed calculate the x and y positions of each item, but will only return a small subset, namely those items which the user is currently looking at. My full grid may be 10000x10000 pixels in size, but my flash player display will only be say 800x600, so, depending on also the current scrollbar values of my view, I can pass a fixed Rectangle box to Alchemy and only items within that box are returned. But that's a different story. Back on topic...
Now I read a post a long time ago by Ralph Hauwert which solved my issue.
Basically you obtain a reference to the fast LEByteArray which is used by the Alchemy machine state, obtaining that reference in AS3 is easy:
-
if (!_clib) {
-
const cLibInit:CLibInit = new CLibInit();
-
const ns:Namespace = new Namespace("cmodule.OrganizationChartLayout");
-
const gstate:MState = ns::gstate;
-
-
_clib = cLibInit.init();
-
_memory = gstate.ds;
-
_mstate = gstate;
-
}
That's just a few lines of code! Anyway, proceeding this, I can now access the Alchemy memory directly to get or set values without the need of doing that through an exposed C function.
Now since I want an exact replica of my data provider, I've decided to reserve memory in Alchemy for each individual property of my data provider item.
In my case, I need an ID, ownerID, isCollapsed, ... etc.
On top, I also need to know in AS3 where exactly in my Alchemy memory the different reserved memory chunks start.
To do this, I present the pointers to these chunks to AS3 :
-
// the c variable
-
int* _data_id;
-
-
// allocate memory to it, you need to know the length of the data provider, if that length is altered in AS3, you need to free this chunk first
-
_data_id = malloc(dp.length * _INT_SIZE);
-
-
// now we provide a method which AS3 can call
-
AS3_Val getDataPointerForId(void* self, AS3_Val args) {
-
return AS3_Ptr(_data_id);
-
}
Still, basic Alchemy stuff going on, in AS3 we can now do something like this:
-
_memoryPointerCollection.idPointer = _clib.getDataPointerForId();
-
-
_memory.position = _memoryPointerCollection.idPointer;
-
_memory.readInt();
-
_memory.writeInt();
Now this is already quite fast, but no fast enough. I want to support thousands of data provider items, so looping over my data in AS3, and setting property values one by one for each item through memory is still slow.
That is because when you write to the Alchemy memory from AS3, you are NOT using the special memory opcodes presented via Alchemy!
Of course, if I would be able to use those opcodes in AS3 as well, my problem would be solved, so here's how you can do that.
2. Optimize the Alchemy compiling
I've recently stumbled upon a discussion with Bernd Paradies, which is a VERY interesting read.
You can replace the memory function bodies with inline assembler code >>>
First, we need to tweak the as code which the Alchemy compiler uses to generate the swc.
This as code has a major optimization possibility, in order to edit it, we first need to tweak the gcc compiler so that we can keep the generated files from the compiler.
Open the gcc file, which can be found in alchemy/achacks and remove the last few lines:
if(!$ENV{ACHACKS_TMPS})
{ sys("rm", "-f", <$$.achacks.*>) }
Next run the Alchemy compiler, so that the as file is generated.
Once you have this file, open it up in a text editor and look for the following code, somewhere at the very top of the file:
-
public class MemUser
-
{
-
public final function _mr32(addr:int):int { gstate.ds.position = addr; return gstate.ds.readInt(); }
-
public final function _mru16(addr:int):int { gstate.ds.position = addr; return gstate.ds.readUnsignedShort(); }
-
public final function _mrs16(addr:int):int { gstate.ds.position = addr; return gstate.ds.readShort(); }
-
public final function _mru8(addr:int):int { gstate.ds.position = addr; return gstate.ds.readUnsignedByte(); }
-
public final function _mrs8(addr:int):int { gstate.ds.position = addr; return gstate.ds.readByte(); }
-
public final function _mrf(addr:int):Number { gstate.ds.position = addr; return gstate.ds.readFloat(); }
-
public final function _mrd(addr:int):Number { gstate.ds.position = addr; return gstate.ds.readDouble(); }
-
public final function _mw32(addr:int, val:int):void { gstate.ds.position = addr; gstate.ds.writeInt(val); }
-
public final function _mw16(addr:int, val:int):void { gstate.ds.position = addr; gstate.ds.writeShort(val); }
-
public final function _mw8(addr:int, val:int):void { gstate.ds.position = addr; gstate.ds.writeByte(val); }
-
public final function _mwf(addr:int, val:Number):void { gstate.ds.position = addr; gstate.ds.writeFloat(val); }
-
public final function _mwd(addr:int, val:Number):void { gstate.ds.position = addr; gstate.ds.writeDouble(val); }
-
}
Next, replace that code block with this inline assembler code block:
-
public class MemUser
-
{
-
public final function _mr32(addr:int):int { return __xasm<int>(push(addr), op(0x37)); } // li32
-
public final function _mru16(addr:int):int { return __xasm<int>(push(addr), op(0x36)); } // li16
-
public final function _mrs16(addr:int):int { return __xasm<int>(push(addr), op(0x36)); } // li16
-
public final function _mru8(addr:int):int { return __xasm<int>(push(addr), op(0x35)); } // li8
-
public final function _mrs8(addr:int):int { return __xasm<int>(push(addr), op(0x35)); } // li8
-
public final function _mrf(addr:int):Number { return __xasm<int>(push(addr), op(0x38)); } // lf32
-
public final function _mrd(addr:int):Number { return __xasm<int>(push(addr), op(0x39)); } // lf64
-
public final function _mw32(addr:int, val:int):void { __asm(push(val), push(addr), op(0x3c)); } // si32
-
public final function _mw16(addr:int, val:int):void { __asm(push(val), push(addr), op(0x3b)); } // si16
-
public final function _mw8(addr:int, val:int):void { __asm(push(val), push(addr), op(0x3a)); } // si8
-
public final function _mwf(addr:int, val:Number):void { __asm(push(val), push(addr), op(0x3d)); } // sf32
-
public final function _mwd(addr:int, val:Number):void { __asm(push(val), push(addr), op(0x3e)); } // sf64
-
}
Perfect! The methods now use inline assembly code to read and write from memory.
Next step is to run the Alchemy compiler once more, but first edit the gcc file again, so that it does not overwrite your custom as file.
To do this, do a search/replace in the gcc file and rename all String values containing $$.achacks to whatever you named your custom as file to.
Mine is called FAST.achacks.as, and in order not to overwrite my as file, change the following line in gcc:
#sys(@llc, "-o=".($last = "FAST.achacks.as"), @ll, @oo);
// just adding the # sign
Now recompile, your generated swc file will have been created with your custom as file.
If you would run the Alchemy swc file through swfdump, you'll notice the correct opcodes are in place, for example:
function cmodule.OrganizationChartLayout:MemUser:::_mr32(:int)::int
maxStack:1 localCount:2 initScopeDepth:4 maxScopeDepth:5
getlocal0
pushscope
getlocal1
OP_0x37 // this is what we want in place
returnvalue
0 Extras
0 Traits Entries
3. Fast Alchemy memory opcodes in AS3
The final step is to update the AS3 code.
If you go back up a little, where I init the clib in AS3, I store a reference to the machine state using _mstate.
This _mstate has the tweaks made in the custom as file accessible, so I can now update my slow readByte/writeByte calls accordingly:
-
// filling the memory chunks
-
for (i=0; i<len; i++) {
-
listItem = LayoutData(_list.source[i]);
-
l4 = int(4*i);
-
-
_mstate._mw32(_memoryPointerCollection.idPointer+l4, listItem.id);
-
_mstate._mw32(_memoryPointerCollection.ownerIdPointer+l4, listItem.ownerId);
-
_mstate._mw32(_memoryPointerCollection.statePointer+l4, listItem.state);
-
_mstate._mw32(_memoryPointerCollection.collapsedPointer+l4, listItem.collapsed ? 1 : 0);
-
_mstate._mw32(_memoryPointerCollection.layoutDeltaPointer+l4, listItem.layoutDelta ? 1 : 0);
-
_mstate._mwf(_memoryPointerCollection.minimizedWidthPointer+l4, listItem.minimizedWidth);
-
_mstate._mwf(_memoryPointerCollection.minimizedHeightPointer+l4, listItem.minimizedHeight);
-
_mstate._mwf(_memoryPointerCollection.normalWidthPointer+l4, listItem.normalWidth);
-
_mstate._mwf(_memoryPointerCollection.normalHeightPointer+l4, listItem.normalHeight);
-
_mstate._mwf(_memoryPointerCollection.maximizedWidthPointer+l4, listItem.maximizedWidth);
-
_mstate._mwf(_memoryPointerCollection.maximizedHeightPointer+l4, listItem.maximizedHeight);
-
}
-
-
// reading the memory chunks
-
while (i) {
-
i4 = 4*(--i);
-
-
memoryIndex = _mstate._mr32(_memoryPointerCollection.visibleItemsPointer+i4);
-
-
m4 = 4*memoryIndex;
-
-
layoutData = LayoutData(_list.source[memoryIndex]);
-
-
ownerMemoryIndex = _mstate._mr32(_memoryPointerCollection.ownerVisibleItemsPointer+i4);
-
-
layoutData.id = _mstate._mr32(_memoryPointerCollection.idPointer+m4);
-
layoutData.ownerId = _mstate._mr32(_memoryPointerCollection.ownerIdPointer+m4);
-
layoutData.x = _mstate._mrf(_memoryPointerCollection.xPosPointer+m4);
-
layoutData.y = _mstate._mrf(_memoryPointerCollection.yPosPointer+m4);
-
layoutData.hasChildren = Boolean(_mstate._mr32(_memoryPointerCollection.hasChildrenPointer+m4));
-
layoutData.collapsed = Boolean(_mstate._mr32(_memoryPointerCollection.collapsedPointer+m4));
-
layoutData.layoutDelta = Boolean(_mstate._mr32(_memoryPointerCollection.layoutDeltaPointer+m4));
-
layoutData.state = _mstate._mr32(_memoryPointerCollection.statePointer+m4);
-
layoutData.listIndex = memoryIndex;
-
layoutData.ownerListIndex = ownerMemoryIndex;
-
-
layoutDataCollection[i] = layoutData;
-
}
This code compiles without issues, and since I can now use the fast memory opcodes to communicate between AS3 and Alchemy, a big performance boost is achieved.
4. Next steps
Recently, the ascommons project was updated with runtime class generation, a very cool addition which you can read all about on here.
We could write dynamic as3 methods which communicate with the Alchemy memory pool, I'll soon start playing around with it to see what is possible...
So, this concludes this optimization step. As I continue to work on the organization chart some new tweaks might pop up, and I'll be sure to post it here.
Frank.
Organization Chart setup guideline
Quite a few people have emailed me saying they cannot get the org chart up and running with the source from Google code.
The demo app is indeed broken at the moment, I've continued working on the component in a different code base, and haven't had the time yet to update the demo application.
Here's how you can create a fix yourself :
In the demo, a series of random generated Objects is being pushed into the dataProvider.
The old version would transform those Objects to LayoutData Objects automatically, in the new version however I've decided to drop that feature.
Now you will need to pass LayoutData Objects directly to the dataProvider, and you must also setup a few required parameters :
This code block would create a valid LayoutData Object :
// set id, ownerId, state, collapsed, disconnected, itemRendererSkin and so on...
data.minimizedWidth = 30;
data.minimizedHeight = 120;
data.normalWidth = 120;
data.normalHeight = 160;
data.maximizedWidth = 400;
data.maximizedHeight = 300;
Notice that the width and height parameters for each Skin state are required. This is not ideal, in future this will be set in the CSS file, with the option to override the CSS style in each LayoutData Object.
Furthermore, make sure you use the latest Flex SDK, or at least 4.0 and upwards, and target Flash Player 10 as mimimum.
My apologies for the trouble, if you still need help, feel free to shoot me an email.
As soon as I have some more time, I'll update the demo app accordingly.
Flex / Spark Organization Chart (v2)
Rebuilt from scratch
Due to the popularity of the original organization chart (post can be found below) and the demand for it, I've rebuilt the component and made it available as open source.
Not that I love creating hierarchical data charts (though I must admit, it is fun), but the previous version was not ready to be openly released.
It didn't follow Flex standards, the datasource had to be an XML file, etc... Not that the component is no good, it was just too specifically designed for the client back then.
In short, here's the new version, and it boasts quite a few good features :
- Full Spark component architecture, meaning everything you see can be spark-skinned.
- Support for massive amouts of nodes
- Data can be any IList, in the demo it is an ArrayCollection. As with any Flex/Spark component, direct changes to the dataProvider collection result in the component updating itself, and vice-versa.
Layout calculations through Alchemy
Besides building it on Spark, the other new feature is that it crunches most calculations using Alchemy.
The layout is done with Alchemy, using a recursive function, which calculates every x and y position of each node.
Afterwards, another Alchemy function will return only those nodes that need to be rendered.
For example, you have a dataprovider of 1000 nodes, Alchemy will position each node, resulting in a viewpane of 10000x1000 pixels for example, yet the component itself in the Flash player is only 400x400 in size. For that 400x400 rectangle, Alchemy will return which nodes fit into that pane.
As a result, the player will never render all 1000 items, yet only those viewable.
As you scroll, the rendererd nodes will be recycled as Alchemy returns different nodes as you scroll.
To gain as much speed as possible, Alchemy function calls are kept to a minimum, and changes to the dataProvider are transferred directly into Alchemy memory.
When done calculating, the component in its turn will read all data from Alchemy by reading directly from the Alchemy memory ByteArray.
Design freedom
As noted above, the whole component can be skinned as you would any other Spark component.
Now 3 states are supported, minimized-normal-maximized.
In the demo, the maximized state implements an Image component and a DataGrid component.
The line connectors between 2 nodes can also be altered with the skin, where you can specify if lines should enter or exit from top, bottom, left or right of the node.
Bottleneck?
Even though I'm happy with the result, since you have to keep in mind that the whole chart starts out with all nodes expanded, the current bottleneck appears to be the rendering of many nodes on screen at once. The current skin does use a lot of gradients, but I'll try to make it go smoother over time...
*** update *** I upped a version boasting 10000 items, which might criple performance on lower end computers.
Click on the image below or here to go to the style explorer.
If you are looking for the source, I've placed it at google code.
It'd be appreciated if you post your performance experience, I've set the item count on 5000 for the moment, which should make it run reasonably adequate on most computers.
Unfinished project #8267909202841
Well...
Title kind of speaks for itself!
I had this vision of my (smart)phone filling up with water, which you could shift using the accelerometer.
In other words, tilt the phone and the water starts flowing.
On top I envisioned it to be running in a maze, so you could get a decent puzzely kind of game out of it, for example by adding some floating or swimming objects which need to get from A to B and maybe even a C.
Alas, lack of time (and honestly the engine is slowing the player down too much) prevented this thing from ever surfacing.
Nevertheless, since this is after all a labs thing, feel free to toy around with is here!
Behind the scenes everything is done with Alchemy, apart from some filters applied in AS3.
It is reasonably speedy, but it will (for now) not quite run yet on any uber-specced smartphone :-/
Flex Organization Chart Component
It's been one hell of a long time since my last update.
Ever since I've been working as a freelancer, time to spend experimenting with Flash has been very few.
Nevertheless, I'd like to showcase this custom built Flex component :
As you may notice by the choice of image, it was developed for Telenet, as a way to represent their organizational structure.
The component is fed by a zipped XML file, containing up to 3000 profiles.
Code has been optimized to display loads of profiles at once, and keeping performance on par.
The component also boasts drag and drop features and is fully skinnable.
Top-right you'll notice a small menu, use it to go either fullscreen, or to start looking for a specific person. Do keep in mind that all data has been replaced with dummy data, normally you can look for firstname, lastname and department in the input field.
Last but not least, the data generating this chart, is retrieved from Org Publisher. I've witten a Java parser to convert the OCB filetype to a ZIP which this Flash loads. So you could view this component as a glorified Org Publisher frontend.
The component has already been reused in a different project, where the profile data is replaced by Flex charts, displaying just how well a certain employee has performed over the past year. You'll understand I can't just put that online
More updates coming soon, there's much in the pipeline, just need to find some time!
Astar pathfinder with Pixel Bender and Alchemy
Some time ago I wrote a simple pathfinder astar implementation in AS3. It was just a simple grid, with walkable and non-walkable nodes which the alorythm used to define the optimal path.The biggest limitation was that everything is always grid-based. You would simple flag certain nodes in the grid as walkable and not walkable and then calculate the best path.
Why Pixel Bender for a path finder?
My initial goal was to have the astar pathfinder run on a pure black and white bitmap data object. This way a designer could simply place complex objects on a map and a developer could easily transform the map to a black and white collision map.
The next step of course would be to have Flash analyse this bitmap and create a grid for pathfinding accordingly.
Using getPixel in a loop would be very slow, especially in very large bitmaps, so Pixel Bender was obviously used here.
A cool bonus is that Pixel Bender also runs asynchronously in a seperate thread from Flash.
How does Pixel Bender create the grid?
The Pixel Bender kernel script takes 2 parameters, one being the black and white bitmap obviously, the other being a node distance value. This node distance is the effective distance between 2 individual nodes in the grid. For example: a bitmap of 600 pixels width would output 30 columns in the node distance is 20 pixels.
As you probably know, Pixel Bender loops over each individual pixel.
For each pixel, the node to the top-left of that pixel is found. Then the script will test if the current pixel happens to be in a straight line between 2 adjacent nodes.
If it is, another test will determine if you can walk from node A to B, this is not the case should the pixel be black.
For each pixel, a result is kept. The output of the Pixel Bender kernel script is image3, so that gives 3 values for each pixel to use.
The first value is a binary value, this binary value is a collection of bit flags for the pixel, namely : is walkable or is not walkable, if the pixel is on the straight line to the node on the right/bottom-right/bottom/top-right.
The other 2 values are used to store the col and row of the top-left node.
For quick updating I also divided the black and white bitmap into areas, each area is 200x200 pixels large and is processed by its own ShaderJob. This way, when the black and white map is updated, I can process these updates by area instead of having to re-process the image as a whole each time.
Now for Alchemy...
Thanks to Ralph Hauwert's excellent post on how to optimize the use of Alchemy and Pixel Bender, I decided to try the same with this experiment.
Flash will write the Pixel Bender result straight into Alchemy memory, where it is then processed in C to a usable grid.
The actual astar algorythm was also ported to C since the algorythm potentially runs a very large loop when using low node distance values or large black and white collision maps.
As a result
The path finder does run faster than in pure AS3, but not by a great amount. You can test it here.
The initial choice is to set the node distance property. The lower the value, the more nodes Pixel Bender will generate and thus the more data Alchemy has to crunch to display the astar path result.
Please note that...
the initial choice can really result in a slow pathfinder if you select 'intensive' for example. In most practical implementations the less-intensive settings are more than enough.
Credit to mr. Doob for the excellent Stats class
c code used in Alchemy
Around 90% of this app was built in C for use in Alchemy, below is the source.
It's a bit cryptic sometimes, mostly due to some efforts in trying to optimize code execution with Alchemy.
Flash is only used to paint the maze and display the result from Alchemy & PB.
XMas in summer
How do you render a 3D Christmas tree in Flash?
Papervision is an option, but don't expect the 3D tree model to look realistic...
This project has a mini, custom-built 3D tree renderer.
It rotates particles around a 3D cone shape, replace those particles with random Bitmap objects of branches, rotate and colorize them randomly, and you get a decent 3D tree feeling.
This build uses 1600 particles, some are dynamic, an they represent pictures which people can upload, sphered within xmas balls.
Click the image to view the 3D tree.
note that the webservice which loads the uploaded xmas balls is currently unavailable, so only the non-decorated tree is shown.
Music@work
Just for fun, and for something else than coding
My first ever attempt at a DJ set
Abfahrt Hinwil - Tech 8
Nicolas Masseyeff ft. jr. C - No more time (Gabriel Ananda mix)
Dirty Vegas - Pressure (Sultan & Ned Shepard remix)
Way Out West - Only Love
Kollektiv Turmstrasse - Freiflug
Elegant Universe feat. Adir Ohayon - Modern Time (Trafik remix)
Hybrid - City Siren (reprise)
BT - Never gonna come back down (Hybrid's Breaktek remix)
Oko - Can't You See
DJ Shadow - Broken Levee Blues (transition rap sample)
Ryan Davis - Clouds Passing By (Eelke Kleijn remix)
Jon Hopkins - Autumn Hill
Eagerly awaiting mobile AIR 2.0/Flash 10.1
It seems like an eternity, waiting for the official release.
Especially with all the commotion recently give Apple's infamous section 331, and the following hatred towards the Flash platform.
I could write a lengthly paper on my thoughts on the matter as a whole, yet those opinions can already be found all over the net in both the Flash and Apple camps.
As I haven't personally had the honour of toying around with, say the Android beta of AIR 2.0, let alone mobile Flash 10.1, I'm just going to await the official release and see if Adobe will manage to silence the negativism surrounding their platform.
Meanwhile, I've prepped up this demo on a rainy day. Very suitably, it is a simulation of running water in a maze. It's an idea I have for a potential Flash game, but again, only time will tell if it will even manage to run well on a mobile device.
Benchmarking on my laptop so far, has given very different results.
In AIR 2.0 beta it runs very well, same with the standalone Flash 10 and Flash 10.1 players, but once loaded in a browser, it becomes painfully slow.
The engine was written in C# with Alchemy, basically the only AS3 code that is used, is to update the main Bitmap object on each frame.
The engine will add water particles every frame, until the count reaches 50000 water particles.
Feel free to have a go by following this link, benchmark posts are more than welcome!
Fun with flash.utils.Proxy and events
UPDATE
Following the below post, some more experimenting gave a much cleaner result, as shown here :
-
var manager:EventManager = EventManager.getInstance(mySprite);
-
// add some event listeners
-
with (manager.assign(myOnMouseEventHandler)) {
-
click++;
-
mouseOver++;
-
mouseOut++; // add listener
-
mouseOut- -; // remove listener
-
}
-
// delete a listener automatically after it has triggered once
-
with (manager.assign(myOnLoadHandler, true)) {
-
complete++;
-
}
-
// use a predefined function to just trace some events
-
with (manager.assign(EventManager.CAPTURE_TRACE)) {
-
complete++;
-
}
Using ++ or - - will add or remove the specified listener for an object.
See the original post below on what's going on.
new source.
ORIGINAL POST
Lately, many people have been complaining about the cumbersome usage of the AS3 events model, we can only dream of a simple implementation in the lines of 'myObject.listeners += dispatcher' instead of 'myObject.addEventListener(eventType, dispatcher, false, 0, true)'.
Now there's really not much of an alternative in AS3, one thing that remotely comes to mind is the flash.utils.Proxy class.
A quick glance at the help page shows promise, you can override accessor function such as getProperty and setProperty. Looking at the typing of the function arguments would suggest that you can pass any data type to these functions, as name, value and return are all noted with an asterix sign.
However, in reality the name argument can be of only two types, String or QName.
That's a bummer because that means you can not get the same functionality which the Dictionary class providers, namely to use a variable of any type as name argument.
Imagine this line of code :
-
EventManager.target(mySprite)[[MouseEvent.CLICK, onClick]]++;
-
// add callback
-
EventManager.target(mySprite)[[MouseEvent.CLICK, onClick]]- -;
-
// remove callback
EventManager is a factory class, it checks if mySprite was previously added as target, if it was the EventManager instance for mySprite which already exists is returned, if it's a first timer, a fresh new EventManager() with mySprite as locally stored 'target' is returned.
Since EventManager extends Proxy, the syntax [[MouseEvent.CLICK, onClick]] would send the Array [MouseEvent.CLICK, onClick] as name:* argument to the setProperty method.
Unfortunately, since name:* is automatically converted to String or QName, the Array gets passed as Array.toString(), tracing to something like "click,function Function() {}".
Okay for click, but the function reference is completely lost now...
Should this have worked without the String cast, I would be able to capture the ++ and - - instructions to actually add or remove listeners.
The setProperty would check if name:* is an Array, and if so store that Array somewhere, then have the setProperty function return a reference to the EventManager instance itself (return this);
++ would be captured then, again by the setProperty method, and would be detectable since the value:* attribute would be 1, or -1 for - -.
Upon reaching to this code, I could address the locally stored event Array from above and call addEventListener or removeEventListener based on the 1 or -1 value.
Being stubborn and all, I did want something useful to emerge out of this experiment. The big trouble is sending the event handler function to the EventManager.
I did end up with the following structure, first imagine the next few code lines :
-
mySprite.addEventListener(MouseEvent.CLICK, onMouseEventA, false, EventPriority.BINDING, true);
-
mySprite.addEventListener(MouseEvent.MOUSE_OVER, onMouseEventA, false, EventPriority.BINDING, true);
-
mySprite.addEventListener(MouseEvent.MOUSE_OUT, onMouseEventA, false, EventPriority.BINDING, true);
-
mySprite.addEventListener(MouseEvent.CLICK, onMouseEventB, false, EventPriority.BINDING, true);
-
mySprite.addEventListener(MouseEvent.MOUSE_OVER, onMouseEventB, false, EventPriority.BINDING, true);
-
mySprite.addEventListener(MouseEvent.MOUSE_OUT, onMouseEventB, false, EventPriority.BINDING, true);
-
mySprite.addEventListener(Event.ENTER_FRAME, onEvent, false, 0, true);
-
mySprite.addEventListener(ToolTipEvent.TOOL_TIP_SHOW, onEvent, false, 0, true);
-
mySprite.addEventListener(ToolTipEvent.TOOL_TIP_HIDE, onEvent, false, 0, true);
That's a long list of code, just to add a number of events to some EventDispatcher.
Now thanks to some fiddling with the return values of the setProperty method, the following line of code (seperated by line breaks and tabs for the sake of readability) does the exact same thing :
-
EventManager.target(mySprite)
-
[MouseEvent.CLICK][MouseEvent.MOUSE_OVER][MouseEvent.MOUSE_OUT][true]
-
(onMouseEventA, EventPriority.BINDING)
-
[MouseEvent.CLICK][MouseEvent.MOUSE_OVER][MouseEvent.MOUSE_OUT][true]
-
(onMouseEventB, EventPriority.BINDING)
-
[Event.ENTER_FRAME][true]
-
(onEvent)
-
[ToolTipEvent.TOOL_TIP_SHOW][ToolTipEvent.TOOL_TIP_HIDE][true]
-
(onEvent);
Notice how you can keep chaining parameters.
The [true] parameter acts as a breakpoint, it tells the setProperty method to apply all event types to the event handler function as noted in the next segment ie (onMouseEventA, EventPriority.BINDING).
When all is applied, it returns yet again a reference to itself (return this), so that future event chaining may proceed.
In the same fashion you can remove listeners :
-
EventManager.target(mySprite)
-
[MouseEvent.CLICK][MouseEvent.MOUSE_OVER][MouseEvent.MOUSE_OUT][false]
-
(onMouseEventA);
-
// removes specific event type and event handler combo.
-
EventManager.target(mySprite)
-
[MouseEvent.CLICK][MouseEvent.MOUSE_OVER][MouseEvent.MOUSE_OUT][false]
-
();
-
// removes ALL event handlers which handle mouseOver and mouseOut
-
EventManager.target(mySprite)[false]()
-
// remove any active event handler for any event type
Is all this worth the trouble? Probably not, but I did have some fun messing with the flash.utils.Proxy class and deviating from its traditional use
Below is a small example of the whole thing in action :
The black box listens to some mouse events (out, over, click)
Should you be interested, the source can be found here.



