[xinf] Events counter-proposal
daniel fischer
dan at f3c.com
Sun Jul 9 14:33:28 CEST 2006
hi all,
after a lot of thinking and shifting things back and forth, i've
finally come up with a proposal for the Event model that i hope
satisifies all. I had nearly given up, settling for either a compromise
in-between, or going for two-different-models-at-the-same-time. But
going thru the discussions again, i found this little hint from
Nicolas: http://xinf.org/pipermail/xinf/2006-June/000107.html .
The trick is to use Type Parameters for functions. I didn't know this
was possible until recently. I've tried and extended Nic's hint, and
the result seems to be "DOM-like Events with haXe type safety". How
does it sound like?
I'm attaching a "complete" example, but let me go thru the basics first.
1) Instead of "pure strings" as event identifiers, we're using Objects
of type EventType<T>:
class EventType<T> {
public var name(default,null) :String;
public function new( name:String ) {
this.name = name;
}
public function toString() {
return name;
}
}
you should note that these do have a "name". That name should be
globally unique (in theory, this could be checked with some static
list, maybe only if some compile flag like -D assureUniqueEvents is
given).
2) There is a (type-parameterized) basic Event class, it's main (here:
only) feature is the event's type:
class Event<T> {
public var type(default,null) : EventType<T>;
public function new( t ) {
type = t;
}
}
3) A concrete Event class looks like this:
class MouseEvent extends Event<MouseEvent> {
static public var MOUSE_DOWN
= new EventType<MouseEvent>("mouseDown");
static public var MOUSE_UP
= new EventType<MouseEvent>("mouseUp");
public var x:Int;
public var y:Int;
public var button:Int;
public function new( _type:EventType<MouseEvent>,
_x:Int, _y:Int, _button:Int ) {
super(_type);
x=_x; y=_y; button=_button;
}
}
we're also defining some event types here-- it seems good to have them
in this class, but they could be anywhere.
4) An EventDispatcher keeps a list of (rather: a hashtable of lists of)
event listener functions, EventDispatcher will likely be an interface
(it is a simple implementation in the attached example), providing some
well-known functions:
interface EventDispatcher {
function addListener<T>( type:EventType<T>, h:T->Void) :Void;
function removeListener<T>( type:EventType<T>, h:T->Void) :Bool;
function dispatchEvent<T>( e :Event<T> ) :Void;
}
this is pretty much all there is to it. The clever use of type
parameters (all credits to Nicolas) assures that you cannot register a
wrong handler function. Here's the common usage pattern:
p.addListener( MouseEvent.MOUSE_DOWN, function(e:MouseEvent) {
// do something.
});
p.dispatchEvent( new MouseEvent(MouseEvent.MOUSE_DOWN,10,10,1));
you can neither register a wrong handler function:
// err: "KeyboardEvent->Void should be MouseEvent->Void"
p.addListener( MouseEvent.MOUSE_DOWN, function(e:KeyboardEvent) { });
nor create a wrong event:
// err: "EventType<KeyboardEvent> should be EventType<MouseEvent>"
new MouseEvent( KeyboardEvent.KEY_DOWN, 10, 10, 1 );
Some more notes:
- *Inside* the EventDispatcher, there is little typesafety; but the
function interface assures proper use. Of course it's easy to
circumvent the typesafety (that is easy in all haXe-- see "untyped"),
but for "normal" usage, this will IMHO be just fine.
- Should you wish, you can do a "generic" handler function that can
validly be registered to any event type:
public static function testHandler<T>( e:Event<T> ):Void {
trace("Generic EventHandler: "+e.type );
}
p.addListener( MouseEvent.MOUSE_DOWN, testHandler );
- the "automatic delegate creation" pitfall remains- this will not work:
public static function testHandler<T>( e:Event<T> ):Void {
trace("Generic EventHandler: "+e.type );
}
p.addListener( MouseEvent.MOUSE_DOWN, testHandler );
p.removeListener( MouseEvent.MOUSE_DOWN, testHandler );
but this will:
public static function testHandler<T>( e:Event<T> ):Void {
trace("Generic EventHandler: "+e.type );
}
var handler = testHandler;
p.addListener( MouseEvent.MOUSE_DOWN, handler );
p.removeListener( MouseEvent.MOUSE_DOWN, handler );
i see no way around this, but it's not too bad a pitfall either.
In the attached Test.hx, i've added a "bubbles" flag to EventType
denoting wether events of this type should bubble or not, and a
"stopped" property/stopPropagation function to Event for obvious
reasons. EventDispatcher is a class (includes implementation), but
should probably be an Interface first, and some kind of
"SimpleEventDispatcher" class, really. I'm also doing some more test
event types- KeyboardEvent and GenericEvent. Note that the
GenericEvent-type used (SOME_EVENT) is not declared in the GenericEvent
class-- there's no need to do so, if you prefer otherwise.
I'm pretty happy with this solution as it seems to provide "best of
both worlds" - you can rather simply add typesafe events and handler
functions, but you can also handle events abstractly if you wish.
Your comments are very appreciated, if the xinf list can
agree on this, i'll of course also post it to the haXe list.
There is (at least) one open issue left- is the order of event
listeners relevant? Probably yes, and EventListener should provide a
way to register listeners with specific priorities... What'cha think?
It seems Nicolas is on holiday- so we have some time to discuss before
he overthrows everything again :)
-dan
--
http://0xDF.com/
http://iterative.org/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Test.hx
Type: application/octet-stream
Size: 4059 bytes
Desc: not available
Url : http://xinf.org/pipermail/xinf/attachments/20060709/f1267a55/attachment.obj
More information about the xinf
mailing list