September 13, 2009

AS3 Signals Getting Stronger

A lot has happened in the last four days.

What Happen? We Git Signal


The majority of commenters preferred Github to Google Code so I moved the Signals project there:

http://github.com/robertpenner/as3-signals

I am experimenting with mirroring back to the Google Code SVN. It's going ok so far.

Committing Early and Often


I made about 20 commits to Github this weekend. Some highlights:
  • The package is now org.osflash.signals (previously com.robertpenner).
    I wanted it to have more of a community feel. Thanks Aral for the namespace!
  • Listener priority is now supported in ISignal.add().
  • ISignal.dispatch() can now send any number of arguments to listeners.
    Zero or ten, it's up to you.
  • Various classes and APIs were renamed for clarity.
  • More unit tests and fixes.

Going Native


Since my last post about connecting EventDispatchers to Signals, I had another idea for integration:

Why not use EventDispatcher for the actual dispatching but wrap it in a Signal facade?

Presenting the NativeSignal class, which lets you have your cake and eat it too.
  1. Take any EventDispatcher, e.g. Sprite.
  2. Create a NativeSignal that targets an event of the dispatcher:
    // in a subclass:
    click = new NativeSignal(this, 'click', MouseEvent);
    
    // or decorating an instance:
    click = new NativeSignal(theDispatcher, 'click', MouseEvent);
  3. Enjoy the Signal APIs and features.
  4. Dispatch from the NativeSignal or the EventDispatcher. Both use Flash's native dispatchEvent()
If you're hesitant to put your trust in new dispatching code, or you want to keep your EventDispatcher options open, this is the gateway drug for you. You don't have to give up anything. All the native functionality stays, and the ISignal interface can be piped in like frosting, wherever you like. Doesn't that sound delicious?

Get Connected

27 comments:

Anonymous said...

I noticed that if the eventObject to be dispatched is not null but evaluates to false in a boolean contex in Signal.as, line 129 and 140, the test fails.
maybe it makes sense to change:

129 if (args.length && eventObject)
140 if (!eventObject)

to:

129 if (args.length && eventObject != null)
140 if (eventObject == null)

Robert Penner said...

Good catch, Anonymous. I'll make a unit test that captures this bug and fix. Unless you want to fork the repo and do it yourself. =)

Jackson Dunstan said...

Hey Robert. I'm glad you decided to go ahead and implement a signals/slots system. I'm really interested to see where it leads. I perused the source code a little bit and so far my favorite feature is addOnce(). I think I'll add that to the event system I'm currently using.

alecmce said...

Hi Rob,

If I addOnce then add, the function reference is maintained in the onceListeners dictionary. I'm not sure what the preferred behaviour is, but it feels to me that the 'onceListeners' reference should be removed.

Robert Penner said...

Alec, that's an interesting point. With addOnce() currently, "the first one wins". Your suggestion would be "the last one wins".

I looked at what flash.events.EventDispatcher does with listener priority, which has the same issue. They chose "first one wins". The documentation specifically says that if you want to change the listener priority, you have to remove the listener and then add it with the new priority. I imagine this is to make it more difficult to accidentally change the priority.

I am inclined to follow the priority pattern for addOnce() so they behave the same way.

Robert Penner said...

Hi Jackson, thanks for pointing me to Qt signals/slots. It was a source of inspiration and provided a catchy name. =)

alecmce said...

I think my instincts are that add trumps addOnce; and even maybe that addOnce throws an error if add is already defined (though that makes things messier internally).

addOnce is weaker than add and is a special case. That said, first call wins is easier to declare as a principle so you're probably right.

Simone said...

Hi Robert , AlecMce just explained me his thoughts (he works 45 cm away from my desk :) ) about add and addOnce and I agree with him.

When you specifically add a signal using only add it means that you are strongly managing it so your code is expecting to handle that signal every time it's dispatched.

If then you try and addOnce the same listener you should get an error.
Now the question could be: what if I REALLY want to add that listener once and so override the previous add functionality?

You can either try catch the addOnce or if you know about it just remove the listener before adding it once.

On the other case if I addOnce first and then add , the last operation will override the first silently.

I would expect that add has always the priority on addOnce.

hope I explained my thoughts well :)

Robert Penner said...

Hi Simone,
Thanks for the explanation. I understand it, but I don't see a big win in exchange for the added logic and documentation necessary. I would rather keep the behavior simple and consistent with priority, so other users don't have to deal with the subtleties.

Simone said...

Fair enough :)

I think the big win would be saving debugging time for devs and avoiding nasty bugs in the long term.

Btw good job it was time to evolve the Flash event system :)

secoif said...

I may be missing something, but non-display classes still can't bubble events, right? Not unless they have a 'parent' and the parent implements IBubbleEventHandler?

I'm getting around this in my Flex application by simply having an implements attribute on my main mxml file:

<s:Application ...
implements="org.osflash.signals.IBubbleEventHandler" />

and then in my non-display class:

public function get parent():Application {
return Application(FlexGlobals.topLevelApplication);
}


This way, I can have a degree of global event handling. This could be useful for something like: printing error messages to a statusbar without having to have that statusbar explicitly listening for every error.

If there's a better way of doing this, let me know. It'd be good to see some more code examples.

Tim Oxley said...

...oh and of course your topLevelApplication (eg Main.mxml) must actually implement IBubbleEventHandler:

public function onEventBubbled(event:IEvent):void {
//Handle Event Here
}

Robert Penner said...

@secoif, that looks right.

kzm said...

Hey...

.. I used addOnce on NativeRelaySignal, but it wouldnt fire. Looking into your code i saw, that you decided to override add/remove but not addOnce/removeOne in the NativeRelaySignal class. Is there a particular reason for this?

And why do you have NativeSignal not implement ISignal.. Makes expensive to switch between NativeRelaySignal and NativeSignal. Reason for this?

Robert Penner said...

@kzm,
Excellent points.
I didn't override addOnce() because until recently, Signal.addOnce() called add(). But I should have had a test for NativeRelaySignal.addOnce(), which would have broken. Feel free to fork on Github and write the test and/or fix. Otherwise I'll get to it in the next couple of days.

The reason for the different interfaces is that IEventDispatcher.dispatchEvent(event:Event) is much more rigid than dispatch(eventObject:Object = null, ...args).

I could have tried to have NativeSignal implement dispatch() with extra args which would be ignored, but it seems like a lie--it implies you can use it in a way that is not implemented. And it would allow more incorrect code through the compiler.

Also, with NativeSignal, target is always an IEventDispatcher. If I left it as Object, it would require casting.

But all signals implement IListeners. The difference is only in IDispatcher vs INativeDispatcher. You can expose your signals as IListeners in your public APIs. Then to dispatch() you cast them to INativeDispatcher or IDispatcher internally. That allows you to prevent anyone else from calling dispatch() from outside your class.

Anonymous said...

Thanks for the code and signals look interesting but I wish it was on google svn which I find very easy to use rather than github which I do not.

Ariel said...

Thanks for the project. Very useful.

Being able to specify signals in an Interface was a major reason I started using signals. That being said, I can't specify the args that will be received in the listening method from the dispatch call.

How do you suggest we handle this so that consumers of an interface will have an idea of what args will be available in the listener?

JTtheGeek said...

Robert, Thanks so much for Signals! We have been building a pretty major web application over at Dedoose and I was running into some serious performance issues with Axiis Visualizations running so many frigging events. Converted a lot of that into Signals and performance skyrocketed! Thanks again for your awesome contributions to the community!
~ JT

shaman4d said...

Hi Robert.

I'm very interesting for your project. But I have one question. What about productivity when need to dispatch hundreds events? What will be faster and eat lower memory native Flash event or yours signal?

Robert Penner said...

@shaman4d,
AS3 Signals use less memory than Events because you don't have to create any new objects during the dispatch. Performance tests have shown that AS3 Signals dispatch faster than Events:
http://github.com/robertpenner/as3-signals/wiki/Performance

divillysausages said...

Hey Robert,
This is nice work!
Why did you go for Arrays over Vectors? Is it to support FP9 and lower?

In remove() in Signal.as, you call a delete on the onceListeners Dictionary, but don't null the key. As the Dictionary isn't set up for weak keys, this will keep the reference to the function, leading to a potential memory leak :)

Also, if you lazy create the Dictionary, you'll save memory on Signals that don't use the addOnce() functionality

Sammy said...

Download a free AS3-Signals Presentation PDF and AS3-Only project here!

http://www.blog.rivellomultimediaconsulting.com/posts/as3-signals-introduction-presentation

-Samuel Asher Rivello
http://www.RivelloMultimediaConsulting.com

Paul Moore said...

Hi Robert!

I ported your AS3 Signals over to Java. I thought you might like to check it out.

The project is hosted on github here:
http://github.com/paulmoore/Java-Signals

fxkill said...

I really wish processing(java) had an event system as simple as signals. I can hardly figure out what they are talking about with these Observer/Obervable things. I'm also hoping that someone says "Hey, there is something like signals for processing".

Damodar Bashyal said...

yes Github is more preferable to Google code.

Anonymous said...

Hey, why arent you still working on the signals

Anonymous said...

What's with all the ISlot stuff? It seems overly complex. Why doesn't a signal simply use a Vector of prioritized functions such as a Vector of a class like "PriorityListener" that allows a priority to be included with the function, such that you simply insert the function in the list after the last function of a given priority (i.e. last added gets called last), using the built-in Vector interface. The type checking is redundant since it only occurs during a dispatch, which AS3 will throw an error on argument count or type mismatches on its own. Using an immutible list requires a complete reconstruction of the listener chain every time you modify it, which is inefficient. I wrote all the functionality of the AS3 Signals project in a single class, and it's about 1/100 the number of lines and actually provides more functionality. WTF.