Announcements Framework

Introducing Announcements

November 21, 2005 14:08:14.144

The preview directory of VisualWorks 7.4 will include Announcements, a new event notification framework that will be used in new VisualWorks components instead of the "traditional" triggerEvent: system. Pollock will change to using announcements in Feature Set 3.

It's also published to Open Repository as packages System-Announcements and System-Announcements-Tests.

This is the first of what will hopefully become a series of posts describing the framework. This one will answer the usual questions I've been asked about it.

Why a new framework for something this fundamental? triggerEvent has been around for a while and seems to be doing the job.

It is doing the job, but poorly once you get beyond the basics. I got especially skeptical about it while implementing some more advanced event handling for tooltips and list item overlay implementation for Pollock. It was much harder than it felt it ought to be. There are other issues as well, such as its very complex implementation--hard to understand and introducing a highly dubious #evaluate method throughout the object hierarchy.

The cause of these problems is simple. triggerEvent is not object-oriented. Events in it are not objects--they are nothing but handler code invocations. This is what's ultimately behind all of the framework's problems.

triggerEvent comes to VisualWorks from Smalltalk/V by way of VisualSmalltalk. Smalltalk/V DOS 2.0 of 1987 vintage already included something very similar to the framework as it is now--though it got more complex over the years. At the time, the decision not to reify events was probably a deliberate and reasonable design choice, to lessen the load on the object memory and garbage collector. There's only so much design purity one can afford in 640K of memory. As events are becoming more important in VisualWorks, the framework is starting to really show its age.

In the new framework, an event is a real object rather than a symbol. It's very much like the ANSI exception framework. An event someone might want to announce, such as a button click or an attribute change, is defined as a subclass of the abstract superclass Announcement. The subclass can have instance variables for additional information to pass along, such as a timestamp, or mouse coordinates at the time of the event, or the old value of the parameter that has changed. To signal the actual occurrence of an event, the "announcer" creates and configures an instance of an appropriate announcement, then broadcasts that instance. Objects subscribed to receive such broadcasts from the announcer receive a broadcast notification together with the instance. They can talk to the instance to find out any additional information about the event that has occurred.

Announcement is such a long name. Why not simply Event? Just now you said "event" more than once while talking about these things.

One problem with Event is that it's about as generic a concept as Object. One could even argue that the objects the announcements framework passes around are indeed announcements rather than events themselves. An event is something intangible, such as the fact that a ValueHolder's value has changed from 2 to 3. An announcement is a tangible notification another object receives to let it know about that change.

Another motivation for using a different name was more pragmatic. We already have a number of other things in the system called events. There are OS input events which are arrays filled by the VM input primitive and handled by InputState. There are image-level input events which are instances of subclasses of UI.Event. There are system events such as #earlySystemInstallation or #aboutToQuit, with SystemEventInterest and related framework to work with those. Even Exceptions are also sometimes called exceptional events.

This creates ample opportunities for confusion, especially when events are related. "The image receives a WM_LBUTTONDOWN event and then as we process MouseButtonPressed event something goes wrong in the handling of the #pressed event". To someone not intimately familiar with all the pieces involved, this adds a few more bits to untangle. Is the argument to triggerEvent: supposed to be an instance of UI.Event? Sounds like it might be.

So to avoid overloading the term even further, the new thing is called Announcement.

What is the new framework like?

Here is a real example of ObservedValue augmented with ChangedValue announcement. ChangedValue has instance variables oldValue and newValue and an instance creation method #from:to:. ObservedValue>>value: method announces the value change as follows:

	...
	changedAnnouncement := ChangedValue from: value to: anObject.
	value := anObject.
	self announce: changedAnnouncement

The code interested in these announcements subscribes to them using something like:

	anObservedValue
		when: ChangedValue
		do: [:change |
			Transcript
				cr; show: 'changed from: '; print: change oldValue;
				show: ' to: '; print: change newValue]

Something like the above already was impossible in the old framework since no values were passed as part of the event. But to spice it up, here is another simple example:

	anObject
		when: Announcement
		do: [:announcement | Transcript cr; print: announcement]

This implements an announcement spy. Just like with exceptions, a subscription to an announcement class will also receive an instance of any subclass of that class. So, a subscription for Announcement will receive any announcement broadcast by anObject.

So events are now objects instead of symbols. What's the big deal? The old events can have arguments too.

The big deal is that they are objects. In follow-up posts I'll point out the things trivial in the new framework that were hard or impossible in the old one. But here are some obvious differences.

When you receive an announcement, you get a real object describing the event you are notified about. Any additional information comes as part of that instance rather than extra arguments the handler method should be prepared to accept. That means that you can funnel multiple announcements into a single piece of handler code. This was almost impossible with triggerEvent, since an event there has no "substance"--it's nothing but the fact of invoking the handler. A handler cannot handle multiple events if they have a different number of arguments, and you cannot tell which event in particular caused the handler to run.

New events introduce announcement taxonomy you can use for "wholesale" processing, like the spy example above.

Because announcements are classes, they can be documented. Look at #eventsTriggeredDeclaration on the class side of Pollock's AbstractPane and Button. What's the difference between the #clicked event and the #pressed event? Were those events real classes, you could read their class comments and find out.

More about documentation. The #boundsChanging: event has an argument. What is that argument? All we know about it is that it exists (and even that is only by convention to use keyword selectors for events with arguments). Is it a Rectangle with the pane's new bounds? Or the old bounds? We have no way of knowing. If BoundsChanging were a class, the argument would be an instance variable with at least a meaningful name, at best a comment.

If you misspell #pressed as #presed in your code you will only notice it when you get a runtime error saying there is no such event. With Announcements, if you misspell Pressed as Presed you immediately get a compiler error saying there is no such class.

With all their benefits, Announcements implementation is much simpler and easier to understand than triggerEvent. Because in triggerEvent events and their arguments are method and block invocations, advanced event processing requires you to write mind-bending block concoctions to tweak the arguments as they are delivered. After you understand the mind-bending framework that stands behind that delivery. Announcement framework needs none of that complexity--all it does is passes a single object around. The implementation is smaller and simpler, at the same time being more flexible.

Announcements sound kind of like Exceptions--why don't you just use Exceptions?

They are like exceptions because they notify about events, but they are different in the nature of relationship between the announcer and the handler. Exceptions are for communication along the sender chain (the stack). There is no explicit connection between the announcer and the handler apart from the fact that the handler calls the announcer, directly or indirectly. Announcements are for communications across the object network, with a connection between the announcer and the handler pre-established by subscribing.

Before signing off for this installment, I want to thank my friends and colleagues who helped this work with their comments and encouragement: Sam Shuster, Steve Dahl, Rich Demers, Travis Griggs, Georg Heeg, and Terry Raymond.