digitalmars.D - Standard Event Driven Object Library for D
- Brian White (71/71) Apr 01 2008 For the last 15 years, I've been working with a custom C++ library for
- Christopher Wright (69/72) Apr 01 2008 % toc:
- Brian White (61/65) Apr 01 2008 The event interface you describe is similar (the same?) as the signals
- Christopher Wright (14/42) Apr 01 2008 I'm seeing two types of events: notifications and responsibilities.
- Brian White (33/48) Apr 01 2008 You could be right. That's why I want to get other people involved; I
- Christopher Wright (13/68) Apr 02 2008 Interesting. I suppose in GUI design, one widget will always or never
- Brian White (14/63) Apr 02 2008 I tried using it under Windows once, setting callback functions for
- Leandro Lucarella (17/20) Apr 01 2008 I guess you're looking for something D-native and more high-level, but
- Brian White (11/15) Apr 01 2008 I'm looking D-native, yes, but really pretty low-level. The generic
- Robert Fraser (3/90) Apr 01 2008 I was thinking about porting SEDA, but Descent is taking all my
- Robert Fraser (3/5) Apr 01 2008 Should have provided the link:
For the last 15 years, I've been working with a custom C++ library for doing event-driven development. Neither of D's "standard" libraries appear to support this kind of development and I was wondering if there is any interested in working on one. That's not to say that you cannot do event-driven development given the current libraries, but it follows the form of: - do a "select" (epoll, whatever) - if socket "a" has a read event, do Xr - if socket "a" has a write event, do Xw - if socket "b" has a read event, do Yr - etc. - repeat What I'm talking about is a library where all the objects handle events and in turn generate their own. When you create a socket, it automatically registers itself with an event loop for that thread. When data comes in, the socket object gets the event with no "user code" required. The socket does what it needs for tracking the connection and then sends an event to (for example) the socket-stream. It does what it needs and calls whatever object is controlling it. In the end, it becomes easy to add self-contained services. A simple HTTP server, for example, can be started with a set of files and absolutely no support from "main". It doesn't have to be passed an open connection, a listening socket, or even a select-server. In addition, the HTTP server itself can make choices about how it receives events, turning them on or off as necessary, or perhaps going so far as spawing new threads. When a new connection comes in, the accepting of a new socket bound to a connection-handler object means that the connection handler is going to get the events associated with that socket. Now it doesn't seem at first glance that this gains you much. If the HTTP server is getting notices directly from a select-server, then it can do a read (or write, as appropriate) to the correct socket and process the results. That's self-contained, too. But when the events always flow from the bottom-up, here are some other things you could get: - Buffering: The socket stream (indeed, the base stream itself) can do read/write bufferring with no support from the top. In the case of a write buffer, in can enable/disable write events when needed with no invervention from the controlling object. And, you only need to write this code once to have it work with HTTP and all other socket services. - Specialization: A select server gives you "read" and "write" events. A socket can generate "read", "write", "connected", "closed", and "broken" events. Why have each network service have to differentiate these independently? - Extensibility: A new feature of the HTTP service is now to accept SIGHUP to reload a set of files. Without touching one line of "main", you simply have the HTTPserver object make a request for events from the global signal handler. Want to time-out connections? Have the socket request events from a timer module and notify the server if a time-out occurs. - Parallelism: It becomes easier (not that multi-threading is ever easy) to run different objects on different execute threads. Since there is no longer a global event loop created/instantiated by "main", the library can make its own decisions (with restrictions, of course). In my C++ code, I did all this with callbacks instead of posted events because, well, it's more efficient and makes better sense than a single event handling function per object with a big switch statement. D's delegates and interfaces should make this even easier. Now it's not to say that this method of programming is without it's difficulties. You can get loops. For example: a socket gets a read event, passes it all the way up to the HTTPserver object which, for whatever reason, decides to close the connection. The "close" call goes down the stack, affects the underlying path, deregisters it from the select-server, and returns... all the way up to the HTTPserver and then back down the event callbacks to the select-server where it generated the read event. The socket was open and valid when it started the callback but now it's closed. You have to plan for this. The other big problem is that you can't do this with an existing library. It has be built-in from the ground up. Doing this in the D library would require a new socket class, a new stream class, etc., etc. It doesn't have to replace the existing one but it would be parallel. What do others think of this? -- Brian
Apr 01 2008
Brian White wrote:What do others think of this? -- Brian% toc: % events % events in stdlib \section{events} I'm also interested in the subject, though I haven't had a chance to work on it. (I'm trying to get a full agile setup in D; I've done mock objects, an xUnit, and dependency injection system so far. Events are on my list, but not yet accomplished.) What sort of API would you want? interface IListener<T> { void Handle(T subject); } class EventPublisher { public void AddListener(object o){} public void Raise<T>(T subject){} } That does a simple O(n) loop on each event to see if any of the registered objects can be converted to an IListener<T>. This is convenient because any class can listen to an arbitrary number of events. It is inconvenient that publishing an event is linear in the number of total listeners rather than the number of listeners for that event. You probably could do some reflection to find which events an object listens to, though. (I wouldn't particularly want to do that in D1, though.) Also, you can't use structs to receive events, if you translate it to D. The other issue is that you need to create a class (or probably struct) for every event you use. CAB and Ayende Rahien's event broker both use strings to describe which event you care about. This means you have fewer UDTs, but then you're using strings, which have zero validation. It also makes it a bit more ugly to provide arguments. Jeremy Miller's system has the best validation -- it's your code that won't compile if you don't listen to the event properly. The worst you could do is send the wrong event and have the wrong stuff happen rather than send an event and have nothing happen -- the former is probably a bit easier to debug. Using strings as event URIs has a benefit that you can use hashing. However, it requires more configuration. Events are easier to work with if you use hooks into a dependency injection tool to wire them up. There is, at last count, exactly one dependency injection library for D, and that's mine. Dconstructor doesn't currently support interceptors that run after the construction of an object. But for event listeners, you could still put a line in the constructor: // if you're using dependency injection this (EventRegistrar events) { events ~= this; } // if you're using the singleton pattern this () { EventRegistrar.instance() ~= this; } \section{events in stdlib} I'm not sure that sockets with events are appropriate for the standard library. You'd need, at a minimum, one extra thread that the sockets operate on, unless you still had a socket set that you polled. This means that any application that used a socket would need to concern itself with thread safety. This would be okay if you had two classes for sockets in the stdlib, say PollingSocket and EventSocket. But still, I would want to control my threads. You could instead have a static method on EventSocket that polls each EventSocket (or uses a SocketSet to do so), and if any socket that has something to report, it will publish the appropriate event. Still, I'm not sure something like this belongs in the standard library quite yet.
Apr 01 2008
I'm also interested in the subject, though I haven't had a chance to work on it. (I'm trying to get a full agile setup in D; I've done mock objects, an xUnit, and dependency injection system so far. Events are on my list, but not yet accomplished.) What sort of API would you want?The event interface you describe is similar (the same?) as the signals module included, but that's not what I'm thinking about. Instead, I'm leaning towards something like: interface PathHandlerInterface { bool PathReadHandler(int path); bool PathWriteHandler(int path); } ...and... interface SocketHandlerInterface { bool SocketSendHandler(Socket s); bool SocketRecvHandler(Socket s); bool SocketConnectedHandler(Socket s); bool SocketBrokenHandler(Socket s): bool SocketClosedHandler(Socket s); } ...and... interface StreamHandlerInterface { bool StreamReadHandler(stream s); bool StreamWriteHandler(stream s); bool StreamOpenedHandler(stream s); bool StreamClosedHandler(stream s); } interface SocketStreamHandlerInterface : StreamHandlerInterface { bool StreamBrokenHandler(stream s); } ...and... class HttpServer : SocketStreamHandlerInterface { ... } The idea, then, is that (for example) an HTTPserver object would derive from SocketStreamHandlerInterface for handing events. The SocketStream class would have a pointer to an attached class and, if non-null, would call those methods off of it whenever it detected the stated condition. That detection would generally be done in Socket*Handler implementations which were called by the attached Socket class. It, in turn, probably called that function from within a Stream*Handler method called by the base Thread object managing the events. As an example... - An EvThread instance does a select and eventually gets a read event on path #N. It does some work and then calls handler.PathReadHandler(pathno). - The EvSocket object that was called as a result does some processing and eventually calls handler.SocketReadHandler(this). - The EvSocketStream object that was called as a result does some processing and eventually calls handler.SocketStreamReadHandler(this); - The EvHttpServer object that was called as a result reads the pending data, does its magic, writes a result, and returns "true" to say it handled the event and wants more of them. - The EvSocketStream object thinks all is good in the world and also returns "true". - The EvSocket object thinks all is good in the world and also returns "true". - The EvThread object things all is good in the world and starts another select loop. Had the event not been handled (returned "false"), it would have disabled read events on that path and continued on. -- Brian
Apr 01 2008
Brian White wrote:As an example... - An EvThread instance does a select and eventually gets a read event on path #N. It does some work and then calls handler.PathReadHandler(pathno). - The EvSocket object that was called as a result does some processing and eventually calls handler.SocketReadHandler(this). - The EvSocketStream object that was called as a result does some processing and eventually calls handler.SocketStreamReadHandler(this); - The EvHttpServer object that was called as a result reads the pending data, does its magic, writes a result, and returns "true" to say it handled the event and wants more of them. - The EvSocketStream object thinks all is good in the world and also returns "true". - The EvSocket object thinks all is good in the world and also returns "true". - The EvThread object things all is good in the world and starts another select loop. Had the event not been handled (returned "false"), it would have disabled read events on that path and continued on. -- BrianI'm seeing two types of events: notifications and responsibilities. A notification just says "This happened, and I don't care if anyone reacts to it." std.signals does okay with that, except for a few bits of ugliness and the fact that it places undesirable dependencies between objects. A responsibility says "This happened, and someone (but only one) should take care of it." It might be an error if nobody handles it. Something can take a look at a responsibility before determining that it's somebody else's problem. However, I don't see the utility in automatically removing a listener when it claims that it cannot handle a particular responsibility. Not handling a particular message and not handling any further messages are different use cases.
Apr 01 2008
I'm seeing two types of events: notifications and responsibilities. A notification just says "This happened, and I don't care if anyone reacts to it." std.signals does okay with that, except for a few bits of ugliness and the fact that it places undesirable dependencies between objects. A responsibility says "This happened, and someone (but only one) should take care of it." It might be an error if nobody handles it. Something can take a look at a responsibility before determining that it's somebody else's problem.That's a good distinction. The word "event" is too loose.However, I don't see the utility in automatically removing a listener when it claims that it cannot handle a particular responsibility. Not handling a particular message and not handling any further messages are different use cases.You could be right. That's why I want to get other people involved; I can't see everything. Here was my reasoning... When doing embedded controller work, I used a very similar design for passing data up a TCP/IP stack to the app. During profiling, I found that a significant amount of the CPU time was being spent in the dispatching of i/o events that just got ignored. By changing the event handlers to say "thanks, but don't send me any more", I was able to save something like 10-15% CPU time. (Profiling never shows you what you expect. :-) In the case of a C++ program in a proper operating system, think of the "write" event from a select call. If you leave it active but write nothing, you'll just get the event again and again and again. You'll spin CPU instead of letting the OS notify you. There are other solutions, of course. In my C++ library, you had to make a call to clear the "write callback" when you didn't want such events. That would work it's way down the stack of objects until it cleared the respective bit from the "write" fd_set. This ended up being somewhat clumsy to implement though because objects are responsible for themselves, it was never anything the library user had to worry about. Instead, I thought I'd take a simpler approach of just requesting "all or nothing" and letting the system take care of turning off those that aren't wanted when they occurred. I figured it would also be beneficial to minimize the number of potential loops in the object stack. If calling "DisableEvent" from within an event handler, you run the risk of that method generating some different event and calling back up the stack yet again. By returning the status, then any other events generated aren't a loop. In the presence of a proper event queue, loops never happens because the next item on the queue doesn't get processed until the previous one completes. Using callbacks, however, this is not the case as every "event" is always processed directly and immediately. -- Brian
Apr 01 2008
Brian White wrote:Interesting. I suppose in GUI design, one widget will always or never want to handle a responsibility. You could return an enum value PassOn, Stop, or SendNoMore. And then the library would want to determine which to return, and just accept a void delegate for the event handler.I'm seeing two types of events: notifications and responsibilities. A notification just says "This happened, and I don't care if anyone reacts to it." std.signals does okay with that, except for a few bits of ugliness and the fact that it places undesirable dependencies between objects. A responsibility says "This happened, and someone (but only one) should take care of it." It might be an error if nobody handles it. Something can take a look at a responsibility before determining that it's somebody else's problem.That's a good distinction. The word "event" is too loose.However, I don't see the utility in automatically removing a listener when it claims that it cannot handle a particular responsibility. Not handling a particular message and not handling any further messages are different use cases.You could be right. That's why I want to get other people involved; I can't see everything. Here was my reasoning... When doing embedded controller work, I used a very similar design for passing data up a TCP/IP stack to the app. During profiling, I found that a significant amount of the CPU time was being spent in the dispatching of i/o events that just got ignored. By changing the event handlers to say "thanks, but don't send me any more", I was able to save something like 10-15% CPU time. (Profiling never shows you what you expect. :-)In the case of a C++ program in a proper operating system, think of the "write" event from a select call. If you leave it active but write nothing, you'll just get the event again and again and again. You'll spin CPU instead of letting the OS notify you. There are other solutions, of course. In my C++ library, you had to make a call to clear the "write callback" when you didn't want such events. That would work it's way down the stack of objects until it cleared the respective bit from the "write" fd_set. This ended up being somewhat clumsy to implement though because objects are responsible for themselves, it was never anything the library user had to worry about.A bit easier to manage if you're using singletons, though that doesn't work in many cases. Or you could pass in whatever created the event along with the event data.Instead, I thought I'd take a simpler approach of just requesting "all or nothing" and letting the system take care of turning off those that aren't wanted when they occurred. I figured it would also be beneficial to minimize the number of potential loops in the object stack. If calling "DisableEvent" from within an event handler, you run the risk of that method generating some different event and calling back up the stack yet again. By returning the status, then any other events generated aren't a loop.I've had some issues with that in the past. I don't think there is a simple solution. Though for GUI stuff, you could often use data binding for simple cases.In the presence of a proper event queue, loops never happens because the next item on the queue doesn't get processed until the previous one completes. Using callbacks, however, this is not the case as every "event" is always processed directly and immediately.What prevents you from adding to the queue an event that will cause this event to be added to the queue again?-- Brian
Apr 02 2008
I tried using it under Windows once, setting callback functions for various events. It worked okay, but didn't allow for multiple watchers. I think for the GUI it's better to use a signals & slots model. It provides more flexibility. I/O, on the other hand, usually has just one chain of objects.When doing embedded controller work, I used a very similar design for passing data up a TCP/IP stack to the app. During profiling, I found that a significant amount of the CPU time was being spent in the dispatching of i/o events that just got ignored. By changing the event handlers to say "thanks, but don't send me any more", I was able to save something like 10-15% CPU time. (Profiling never shows you what you expect. :-)Interesting. I suppose in GUI design, one widget will always or never want to handle a responsibility. You could return an enum value PassOn, Stop, or SendNoMore. And then the library would want to determine which to return, and just accept a void delegate for the event handler.I always include the generator object in the call. That allows a single object to accept from multiple sources, like an HTTPserver with multiple open connections.In the case of a C++ program in a proper operating system, think of the "write" event from a select call. If you leave it active but write nothing, you'll just get the event again and again and again. You'll spin CPU instead of letting the OS notify you. There are other solutions, of course. In my C++ library, you had to make a call to clear the "write callback" when you didn't want such events. That would work it's way down the stack of objects until it cleared the respective bit from the "write" fd_set. This ended up being somewhat clumsy to implement though because objects are responsible for themselves, it was never anything the library user had to worry about.A bit easier to manage if you're using singletons, though that doesn't work in many cases. Or you could pass in whatever created the event along with the event data.Nothing, but there are differences. Any given event gets fully processed before the next one. With callbacks, you can get one within another. In the case of an event adding itself, you effectively have an infinite loop. In the case of a callback calling itself, you have a stack overflow. -- BrianInstead, I thought I'd take a simpler approach of just requesting "all or nothing" and letting the system take care of turning off those that aren't wanted when they occurred. I figured it would also be beneficial to minimize the number of potential loops in the object stack. If calling "DisableEvent" from within an event handler, you run the risk of that method generating some different event and calling back up the stack yet again. By returning the status, then any other events generated aren't a loop.I've had some issues with that in the past. I don't think there is a simple solution. Though for GUI stuff, you could often use data binding for simple cases.In the presence of a proper event queue, loops never happens because the next item on the queue doesn't get processed until the previous one completes. Using callbacks, however, this is not the case as every "event" is always processed directly and immediately.What prevents you from adding to the queue an event that will cause this event to be added to the queue again?
Apr 02 2008
Brian White, el 1 de abril a las 13:12 me escribiste:For the last 15 years, I've been working with a custom C++ library for doing event-driven development. Neither of D's "standard" libraries appear to support this kind of development and I was wondering if there is any interested in working on one.I guess you're looking for something D-native and more high-level, but I guess my D "bindings" for libev[1] could be of your interest. For know, I have just a git repository[2], but there are some examples and tests in the repository you can find useful as "documentation". [1] http://software.schmorp.de/pkg/libev.html [2] http://git.llucax.com.ar/?p=software/ev.d.git;a=summary -- Leandro Lucarella (luca) | Blog colectivo: http://www.mazziblog.com.ar/blog/ ---------------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ---------------------------------------------------------------------------- no longer afraid of the dark or midday shadows nothing so ridiculously teenage and desperate, nothing so childish - at a better pace, slower and more calculated, no chance of escape,
Apr 01 2008
I guess you're looking for something D-native and more high-level, but I guess my D "bindings" for libev[1] could be of your interest. For know, I have just a git repository[2], but there are some examples and tests in the repository you can find useful as "documentation".I'm looking D-native, yes, but really pretty low-level. The generic event throw/catch model (aka "signals & slots") works great for generic concepts like "the user just selected a new color" because many independent objects may be interested in it, or none may be interested. It's very arbitrary. When it comes to I/O, however, things behave somewhat differently. There is typically a chain of objects that are interested and the ability to pass an event up that chain, modifying it or altering it as appropriate, works very well. This has been my experience with my C++ library, anyway. -- Brian
Apr 01 2008
Brian White wrote:For the last 15 years, I've been working with a custom C++ library for doing event-driven development. Neither of D's "standard" libraries appear to support this kind of development and I was wondering if there is any interested in working on one. That's not to say that you cannot do event-driven development given the current libraries, but it follows the form of: - do a "select" (epoll, whatever) - if socket "a" has a read event, do Xr - if socket "a" has a write event, do Xw - if socket "b" has a read event, do Yr - etc. - repeat What I'm talking about is a library where all the objects handle events and in turn generate their own. When you create a socket, it automatically registers itself with an event loop for that thread. When data comes in, the socket object gets the event with no "user code" required. The socket does what it needs for tracking the connection and then sends an event to (for example) the socket-stream. It does what it needs and calls whatever object is controlling it. In the end, it becomes easy to add self-contained services. A simple HTTP server, for example, can be started with a set of files and absolutely no support from "main". It doesn't have to be passed an open connection, a listening socket, or even a select-server. In addition, the HTTP server itself can make choices about how it receives events, turning them on or off as necessary, or perhaps going so far as spawing new threads. When a new connection comes in, the accepting of a new socket bound to a connection-handler object means that the connection handler is going to get the events associated with that socket. Now it doesn't seem at first glance that this gains you much. If the HTTP server is getting notices directly from a select-server, then it can do a read (or write, as appropriate) to the correct socket and process the results. That's self-contained, too. But when the events always flow from the bottom-up, here are some other things you could get: - Buffering: The socket stream (indeed, the base stream itself) can do read/write bufferring with no support from the top. In the case of a write buffer, in can enable/disable write events when needed with no invervention from the controlling object. And, you only need to write this code once to have it work with HTTP and all other socket services. - Specialization: A select server gives you "read" and "write" events. A socket can generate "read", "write", "connected", "closed", and "broken" events. Why have each network service have to differentiate these independently? - Extensibility: A new feature of the HTTP service is now to accept SIGHUP to reload a set of files. Without touching one line of "main", you simply have the HTTPserver object make a request for events from the global signal handler. Want to time-out connections? Have the socket request events from a timer module and notify the server if a time-out occurs. - Parallelism: It becomes easier (not that multi-threading is ever easy) to run different objects on different execute threads. Since there is no longer a global event loop created/instantiated by "main", the library can make its own decisions (with restrictions, of course). In my C++ code, I did all this with callbacks instead of posted events because, well, it's more efficient and makes better sense than a single event handling function per object with a big switch statement. D's delegates and interfaces should make this even easier. Now it's not to say that this method of programming is without it's difficulties. You can get loops. For example: a socket gets a read event, passes it all the way up to the HTTPserver object which, for whatever reason, decides to close the connection. The "close" call goes down the stack, affects the underlying path, deregisters it from the select-server, and returns... all the way up to the HTTPserver and then back down the event callbacks to the select-server where it generated the read event. The socket was open and valid when it started the callback but now it's closed. You have to plan for this. The other big problem is that you can't do this with an existing library. It has be built-in from the ground up. Doing this in the D library would require a new socket class, a new stream class, etc., etc. It doesn't have to replace the existing one but it would be parallel. What do others think of this? -- BrianI was thinking about porting SEDA, but Descent is taking all my programming time up right now.
Apr 01 2008
Robert Fraser wrote:I was thinking about porting SEDA, but Descent is taking all my programming time up right now.Should have provided the link: SEDA website: http://www.eecs.harvard.edu/~mdw/proj/seda/
Apr 01 2008