
From the point of view of the 'client', there is just one more thing: when requesting an event from the server, it can also provide a 'busy interface object'; this object handles cases where an event occurs, but the client (the owner object of the call back function) is 'busy' and cannot handle the event. Since this is part of a library for event driven applications that I wrote, every call should return as soon as possible. So, if the client is 'busy' then the fact that
I've written something simular as is current talked about in the thread 'Interest in a runtime dynamic dispatch library', but since the callbacks are called when some event occurs, I call them 'event servers'. I suppose that might include a 'dispatcher', but I wouldn't have a clu which part ;). And to be honest, I can't think of much that makes this useful without the whole thing. The concept of my 'event servers' is the following: The user declares(/derives) a class which represents an 'event server'. Each event server services events of a certain 'type'. In particular, the type of the data that is returned as parameter of the callback function is fixed per event server (though totally arbitrary per event server class). I call this 'the event type'. Thus, FooEventServer foo_event_server; It is possible to instantiate any number of event servers, even of the same type, though mostly one would just instantiate a single instance for each given 'event type', because each instance of an event server would have it's own queue for events that weren't handled yet. At an arbitrary number of points in the code it is possible to request events of such a type, by calling a method of the server. Such calls pass a reference of an arbitrary class type as well as a member function pointer to a member function of that class type: the call back function. // Request events from foo_event_server. foo_event_server(my_event_client, &MyEventClient::call_back); The only restrictions here are that the callback function must return a bool and takes the corresponding 'event type' as parameter. class MyEventClient { ... bool call_back(FooEventServer::event_type const& event_type); or just bool call_back(FooEventType const& event_type); }; Optionally, it is possible to pass a 'cookie' object (also of arbitrary type), in which case the call back function also needs to accept an argument of the type of this cookie object: the cookie will be returned as-is to the caller, when the event occurs. class MyEventClient { ... template<COOKIE> bool call_back(FooEventServer::event_type const& event_type, COOKIE cookie); }; ... // Request events from foo_event_server. foo_event_server(my_event_client, &MyEventClient::call_back, cookie); // cookie can be anything. Finally, it is possible to define the event server such that a given type (arbitrary, but fixed per event server) can be passed along with the request, I call this the 'event data'. This data object can be used to decide if an event of a given type is really matching the request of that client or not. // Request events from foo_data_event_server. foo_data_event_server(request_data, my_event_client, &MyEventClient::call_back); Once the callback function is called (being passed an object of the 'event type', and optionally the 'cookie' object), it can either return false-- meaning that the request should stay and handled again, or true-- meaning that the request should be removed (it was the last time the call back was called). the event occured should be queued somewhere until it becomes not-busy again; this is done by the 'busy interface object'. I've written a few event servers in the mean time; below are some examples of working code. Please note that this post shows what is possible from the point of view of the 'client' (the one requesting an event, and the object that gets the call back). It doesn't go into the inner workings of the event servers that I wrote. Example 1. This requests a user answer to some text question. Each 'client_session' (a socket connection of the current client (not the event client, that would be current object *this)), has it's own instance of a 'user_answer_event_server' that dispatches the answers of the user behind that client. M_identity->client_session().user_answer_event_server (UserAnswerRequestData(M_identity, "What server should I connect to (type: <server> [<port>])?"), *this, &ServerConnection::received_server_port_answer); The 'request data' of the user_answer_event_server is obviously UserAnswerRequestData, and exists of a pointer to some application specific 'identity', as well as the actual question. When the user answers the question, the method 'received_server_port_answer' is called (of the current object (*this)). Example 2. Each 'server_session' (a socket connection to a server on the internet (not the event server)) has an 'event server' instance, here returned by message_event_server(). MatchRequest match_request(M_identity); match_request(396). // Numeric 396 reply localserver(prefix). // From local server mynick(0). // to me. wildcard("*.users.undernet.org", 1). // Parameter 1. exactmatch("is now your hidden host", 2); // Parameter 2. // Request an event for matching messages. M_server_session->message_event_server()(match_request, *this, &ServerConnection::hidden_host); The last line requests 'hidden_host' to be called back (of the current object) as soon as a message from the server is received that matches the given match_request. Obviously, MatchRequest is an arbitrary custom object (that allows me to very efficiently match incoming server messages) but it is completely linked to this particular event server: it is the 'event data' type of this event server. Example 3. char service_mode = 'x'; ASSERT(M_state == authentication_sent); M_state = umode_i_sent; *M_server_session << "MODE " << M_identity->server_out_nick() << " +i" << service_mode << "\r\n"; UserModeMask mask('i'); mask |= service_mode; // Request an event for an expression to become true. ON_TRUE((*M_identity->user_mode() & ConstExpression(mask)) == ConstExpression(mask), *this, &ServerConnection::invisible_event); Ok, this one isn't entirely fair - as 'ON_TRUE' is a macro (see below). The last line causes the callback 'invisible_event' to be called, again a method of the current object (*this), as soon as the expression given in the first argument of the macro becomes true. The macro is defined as #define ON_TRUE(expr, action...) do { (expr).create_event_server()(action); } while(0) In this case, a temporary event server is created for every event request; one for each expression that has to be evaluated. The to-be-evaluated expression is thus not the event data - but incorporated into the event server a lot more than that. [Here is how this works: user_mode() returns a reference to an object of type Watched<UserModeMask>. By putting the '*' in front of it, it can be used in an Expression as above, otherwise it just acts as a UserModeMask. The fact that it is used in the expression above tells the Watched<UserModeMask> object that it is used in that particular expression. When the Watched<UserModeMask> changes value, it notifies the event server - which in turn re-evaluates the whole expression to see if it became true, calling the call back if it did.] Example 4. This code requests a timer event. 'bi' is a busy interface object in this case: struct timeval timeout = { 2, 0 }; timerRequest(timeout, *this, &alarm_tst_app_ct::its_time, bi); This will call 'its_time' of the current object, after the event data 'timeout' has expired and the busy-interface object (bi) is not set busy -- otherwise the call back will be delayed until the moment that the busy-interface object is set un-busy again. However, the 'event type' (the object passed to the callback function) contains the actual time at which the timer expired-- so, even though the callback might be a little later, the client always is remembered at which the event really occured. As you can see, the number of possibilities are endless. It is therefore not possible to write one interface that fits all. Things like the expression evaluation, the message matching routines and the code that handles the user questions, all have to be customized code, of course. As a result, writing an 'event server' is a lot of work - even when you already provide the frame work, and all possible common code. Nevertheless, the interface provided by the event library makes it possible to get the cookie and busy interfaces for free. And often it is only needed to declare the EventType class (the type of the parameter passed back the call back function), define a base class for event requests, and write the 'trigger' function that actually figures out which request matches the current event. The 'UserAnswer' event server exists of 179 lines of code (including copy right notices and comments in the files). The expression evaluation event server exists of 1247 lines, while the message matcher needed 488 lines of extra code. The timer event server is part of my event library. What I like about this design is that the class types that are used for the cookie as well as the event client (the owner of the call back function) can be totally arbitrary for every event request (while remaining 100% type-safe of course - it doesn't do any casting). Also, the interface for the many ways an event can be requested (with or without cookie, and with or without a busy interface) involving rather complex templated member functions on top of that, could be totally re-used. It is not necessary to put time into that for a new event server. Finally (something I didn't even began to get into in the above), all of this delete-safe. When any object involved is deleted at any moment, then the events simply don't occur anymore and memory is freed as appropriate. Ie, suppose you have a Watched<> object that is being used in multiple expression, but the object owning the Watched<> object is destroyed... no problem. The expression will simply not evaluate to true anymore: the event requests are deleted, the temporary event server is deleted, no pain. Same when an event client that requested the call back is destroyed: that fact automatically causes the event server to not call it's call back method anymore. Deleting an event server, obviously also cleans up any pending events. In order to get this garbage collecting working smoothly, I have added one more demand for event clients: they have to be derived from 'EventClient'. This is not really necessary, but it doesn't seem a problem to me (one of their methods already has to know about the event server, as it takes the 'EventType' as parameter, so the class clearly is event-server-aware anyway). Regards, -- Carlo Wood <carlo@alinoe.com>

On 6/12/06, Carlo Wood <carlo@alinoe.com> wrote:
I've written something simular as is current talked about in the thread 'Interest in a runtime dynamic dispatch library', but since the callbacks are called when some event occurs, I call them 'event servers'. I suppose that might include a 'dispatcher', but I wouldn't have a clu which part ;). And to be honest, I can't think of much that makes this useful without the whole thing.
The concept of my 'event servers' is the following:
The user declares(/derives) a class which represents an 'event server'. Each event server services events of a certain 'type'. In particular, the type of the data that is returned as parameter of the callback function is fixed per event server (though totally arbitrary per event server class). I call this 'the event type'.
[...]
I've also uploaded something very similar to the Boost Vault -- I called it a Listener library where there's a base Listener class, which specific listeners had to derive from. The base class then was in charge of registering the listener to a router, which did the message routing magic. I don't think the listener implementation will fit in a "library" because it's more a framework than anything. Although that's one way of achieving the functionality that the runtime dynamic dispatcher provides. The "dispatcher" really is more a "router" than anything, in that the message that occurred defines which handler to call/invoke and even route the message to. -- Dean Michael C. Berris C/C++ Software Architect Orange and Bronze Software Labs http://3w-agility.blogspot.com/ http://cplusplus-soup.blogspot.com/ Mobile: +639287291459 Email: dean [at] orangeandbronze [dot] com

Carlo Wood wrote:
I've written something simular as is current talked about in the thread 'Interest in a runtime dynamic dispatch library', but since the callbacks are called when some event occurs, I call them 'event servers'. I suppose that might include a 'dispatcher', but I wouldn't have a clu which part ;). And to be honest, I can't think of much that makes this useful without the whole thing.
The concept of my 'event servers' is the following:
The user declares(/derives) a class which represents an 'event server'. Each event server services events of a certain 'type'. In particular, the type of the data that is returned as parameter of the callback function is fixed per event server (though totally arbitrary per event server class). I call this 'the event type'.
Thus,
FooEventServer foo_event_server;
Look at Boost Signals. It allows any function to trigger an event of any type and it allows any function of the correct type to handle the event. By "function" one means a free function, member function, or function object. It has no restrictions as to the class whose member function triggers the event or the class whose member function handles the event. This is not to say that your concept is not a valuable one but just that Boost Signal has a flexibility which many programmers may want in an event dispatching/event handling system, as opposed to a more restrictive yet more focused system.
participants (3)
-
Carlo Wood
-
Dean Michael Berris
-
Edward Diener