The OpenD Programming Language

1 /// My old event loop helper library for Linux. Not recommended for new projects.
2 module arsd.eventloop;
3 
4 version(linux):
5 
6 /* **** */
7 // Loop implementation
8 // FIXME: much of this is posix or even linux specific, but we ideally want the same interface across all operating systems, though not necessarily even a remotely similar implementation
9 
10 import std.traits;
11 
12 // we send custom events as type+pointer pairs. The type is sent as a hash of the mangled name, so we get a unique integer for anything, including any user defined types.
13 template typehash(T...) {
14 	void delegate(T) tmp;
15 	enum typehash = hashOf(tmp.mangleof.ptr, tmp.mangleof.length);
16 }
17 
18 private struct TimerInfo {
19 	WrappedListener handler;
20 	int timeoutRemaining; // in milliseconds
21 	int originalTimeout;
22 	int countRemaining;
23 }
24 
25 private TimerInfo*[] timers;
26 
27 private WrappedListener[][hash_t] listeners;
28 private WrappedListener[] idleHandlers;
29 
30 /// Valid event listeners must be callable and take exactly one argument. The type of argument determines the type of event.
31 template isValidEventListener(T) {
32 	enum bool isValidEventListener = isCallable!T && ParameterTypeTuple!(T).length == 1;
33 }
34 
35 private enum backingSize = (void*).sizeof + hash_t.sizeof;
36 
37 /// Calls this function once every time the event system is idle
38 public void addOnIdle(T)(T t) if(isCallable!T && ParameterTypeTuple!(T).length == 0) {
39 	idleHandlers ~= wrap(t);
40 }
41 
42 /// Removes an idle handler (added with addOnIdle)
43 public void removeOnIdle(T)(T t) if(isCallable!T && ParameterTypeTuple!(T).length == 0) {
44 	auto pair = getPtrPair(t);
45 	foreach(idx, listener; idleHandlers) {
46 		if(listener.matches(pair)) {
47 			idleHandlers = idleHandlers[0 .. idx] ~ idleHandlers[idx + 1 .. $];
48 			break;
49 		}
50 	}
51 }
52 
53 /// An opaque type to reference an active timer
54 struct TimerHandle {
55 	private TimerInfo* ptr;
56 }
57 
58 /// Sets a timer, one-shot by default. Count tells how many times the timer will fire. Set to zero for a continuously firing timer
59 public TimerHandle setTimeout(T)(T t, int msecsWait, int count = 1) if(isCallable!T && ParameterTypeTuple!(T).length == 0) {
60 	auto ti = new TimerInfo;
61 	ti.handler = wrap(t);
62 	ti.timeoutRemaining = msecsWait;
63 	ti.originalTimeout = msecsWait;
64 	ti.countRemaining = count;
65 
66 	// FIXME: this could prolly be faster by taking advantage of the fact that the timers are sorted
67 	bool inserted = false;
68 	foreach(idx, timer; timers) {
69 		if(timer.timeoutRemaining > msecsWait) {
70 			import std.array;
71 			insertInPlace(timers, idx, ti);
72 			inserted = true;
73 			break;
74 		}
75 	}
76 
77 	if(!inserted)
78 		timers ~= ti;
79 
80 	return TimerHandle(ti);
81 }
82 
83 /// Sets a continuously firing interval. It will call the function as close to the interval as it can, but it won't let triggers stack up.
84 public TimerHandle setInterval(T)(T t, int msecsInterval)  if(isCallable!T && ParameterTypeTuple!(T).length == 0) {
85 	return setTimeout(t, msecsInterval, 0);
86 }
87 
88 /// Clears a timer
89 public void clearTimeout(TimerHandle handle) {
90 	size_t foundIndex = size_t.max;
91 	// FIXME: this could prolly be faster by taking advantage of the fact that the timers are sorted
92 	foreach(idx, timer; timers) {
93 		if(timer is handle.ptr) {
94 			foundIndex = idx;
95 			break;
96 		}
97 	}
98 
99 	if(foundIndex == size_t.max)
100 		return;
101 
102 	for(auto i = foundIndex; i < timers.length - 1; i++)
103 		timers[i] = timers[i + 1];
104 	timers.length = timers.length - 1;
105 }
106 
107 public void clearInterval(TimerHandle handle) {
108 	clearTimeout(handle);
109 }
110 
111 /// Sends an exit event to the loop. The loop will break when it sees this event, ignoring any events after that point.
112 public void exit() @nogc {
113 	ubyte[backingSize] bufferBacking = 0; // a null message means exit...
114 
115 	writeToEventPipe(bufferBacking);
116 }
117 
118 void writeToEventPipe(ubyte[backingSize] bufferBacking) @nogc {
119 	ubyte[] buffer = bufferBacking[];
120 	while(buffer.length) {
121 		auto written = unix.write(pipes[1], buffer.ptr, buffer.length);
122 		if(written == 0)
123 			assert(0); // wtf
124 		else if(written == -1) {
125 			if(errno == EAGAIN || errno == EWOULDBLOCK) {
126 				// this should never happen here, because the messages
127 				// are virtually guaranteed to be smaller than the pipe buffer
128 				// ...unless there's like a thousand messages, which is a WTF anyway
129 				import std.string;
130 				assert(0); // , format("EAGAIN on %d", buffer.length));
131 			} else
132 				assert(0, "write failure");
133 				// throw new Exception("write");
134 		} else {
135 			assert(written <= buffer.length);
136 			buffer = buffer[written .. $];
137 		}
138 	}
139 }
140 
141 /// Adds an event listener. Event listeners must be functions that take exactly one argument.
142 public void addListener(T)(T t) if(isValidEventListener!T) {
143 	auto hash = typehash!(ParameterTypeTuple!(T)[0]);
144 	listeners[typehash!(ParameterTypeTuple!(T)[0])] ~= wrap(t);
145 }
146 
147 /// Removes an event listener. Returns true if the event was actually found.
148 public bool removeListener(T)(T t) if(isValidEventListener!T) {
149 	auto hash = typehash!(ParameterTypeTuple!(T)[0]);
150 	auto list = hash in listeners;
151 
152 	auto pair = getPtrPair(t);
153 
154 	if(list !is null)
155 	foreach(idx, ref listener; *list) {
156 		if(listener.matches(pair)) {
157 			(*list) = (*list)[0 .. idx] ~ (*list)[idx + 1 .. $];
158 			return true;
159 		}
160 	}
161 	return false;
162 }
163 
164 /// Sends a message to the listeners immediately, bypassing the event loop
165 public void sendSync(T)(T t) {
166 	auto hash = typehash!T;
167 	auto ptr  = cast(void*) &t;
168 	dispatchToListenerWithPtr(hash, ptr);
169 }
170 
171 import core.stdc.stdlib;
172 
173 /// Send a message to the event loop
174 public void send(T)(T t) {
175 	// FIXME: we need to cycle the buffer position back so we can reuse this as the message is received
176 	// (if you want to keep a message, it is your responsibility to make your own copy, unless it is a pointer itself)
177 	//static ubyte[1024] copyBuffer;
178 	//static size_t copyBufferPosition;
179 
180 	// for now we'll use the [s]gc[/s] malloc. The problem with the gc was it could actually be collected while pending in the pipe. since there's no reference around, if there's a collection between the send and receive, the gc will reap it leaving the receiver with garbage data.
181 	// so instead, I'm mallocing it.
182 
183 	// Might be able to go back to a static buffer eventually but eh for now malloc will do it. I called free() at the end of the receiver function from the pipe.
184 	size_t copyBufferPosition = 0;
185 	auto copyBuffer = (cast(ubyte*) malloc(T.sizeof))[0 .. T.sizeof]; //new ubyte[](T.sizeof);
186 
187 
188 	auto hash = typehash!T;
189 	//auto ptr  = (cast(void*) &t);
190 
191 	// we have to copy the data off the stack so the pointer is still usable later
192 	// we use a static buffer to avoid more allocations
193 	// (if the data is big, it probably isn't on the stack anyway. hopefully!)
194 	auto ptr = cast(void*) (copyBuffer.ptr + copyBufferPosition);
195 
196 	copyBuffer[copyBufferPosition .. copyBufferPosition + T.sizeof] = (cast(ubyte*)(&t))[0 .. T.sizeof];
197 	copyBufferPosition += T.sizeof;
198 
199 	// then we send it as a hash+ptr pair
200 
201 	ubyte[hash.sizeof + ptr.sizeof] buffer;
202 	buffer[0 .. hash.sizeof] = (cast(ubyte*)(&hash))[0 .. hash.sizeof];
203 	buffer[hash.sizeof .. $] = (cast(ubyte*)(&ptr ))[0 .. ptr .sizeof];
204 
205 	writeToEventPipe(buffer);
206 }
207 
208 /// Runs the loop, dispatching events to registered listeners as they come in
209 public void loop() {
210 	// get whatever is in there now, so we are clear for edge triggering
211 	if(readFromEventPipe() == false)
212 		return; // already got an exit
213 
214 	loopImplementation();
215 }
216 
217 public template isValidFileEventDispatcherHandler(T, FileType) {
218 	static if(is(T == typeof(null)))
219 		enum bool isValidFileEventDispatcherHandler = true;
220 	else {
221 		enum bool isValidFileEventDispatcherHandler = (
222 			is(T == typeof(null))
223 			||
224 			(
225 				isCallable!T
226 				&&
227 				(ParameterTypeTuple!(T).length == 0 ||
228 					(ParameterTypeTuple!(T).length == 1 && is(ParameterTypeTuple!(T)[0] == FileType)))
229 			)
230 		);
231 	}
232 }
233 
234 private template templateCheckHelper(bool condition, string error) {
235 	static if(!condition) {
236 		static assert(0, error);
237 	}
238 	enum bool templateCheckHelper = condition;
239 }
240 
241 /// Since the lowest level event for files only allows one handler, but can send events that require a variety of different responses,
242 /// the FileEventDispatcher is available to make this easer.
243 ///
244 /// Instead of filtering yourself, you can add files to one of these with handlers for read, write, and error on that specific handle.
245 /// These handlers must take either zero arguments or exactly one argument, which will be the file being handled.
246 public struct FileEventDispatcher {
247 	private WrappedListener[3][OsFileHandle] listeners;
248 	private WrappedListener[3] defaultHandlers;
249 
250 	private bool handlersActive;
251 
252 	private void activateHandlers() {
253 		if(handlersActive)
254 			return;
255 
256 		addListener(&lowLevelReadHandler);
257 		addListener(&lowLevelHupHandler);
258 		addListener(&lowLevelWriteHandler);
259 		addListener(&lowLevelErrorHandler);
260 		handlersActive = true;
261 	}
262 
263 	private void deactivateHandlers() {
264 		if(!handlersActive)
265 			return;
266 
267 		removeListener(&lowLevelErrorHandler);
268 		removeListener(&lowLevelHupHandler);
269 		removeListener(&lowLevelWriteHandler);
270 		removeListener(&lowLevelReadHandler);
271 		handlersActive = false;
272 	}
273 
274 	~this() {
275 		deactivateHandlers();
276 	}
277 
278 	private WrappedListener getHandler(OsFileHandle fd, int idx)
279 		in { assert(idx >= 0 && idx < 3); }
280 	do {
281 		auto handlersPtr = fd in listeners;
282 		if(handlersPtr is null)
283 			return null; // we don't handle this function
284 
285 		auto handler = (*handlersPtr)[idx];
286 		if(handler is null)
287 			handler = defaultHandlers[idx];
288 
289 		return handler;
290 	}
291 
292 	private void doHandler(OsFileHandle fd, int idx) {
293 		auto handler = getHandler(fd, idx);
294 		if(handler is null)
295 			return;
296 		handler.call(&fd);
297 	}
298 
299 	private void lowLevelReadHandler(FileReadyToRead ev) {
300 		doHandler(ev.fd, 0);
301 	}
302 
303 	private void lowLevelWriteHandler(FileReadyToWrite ev) {
304 		doHandler(ev.fd, 1);
305 	}
306 
307 	private void lowLevelErrorHandler(FileError ev) {
308 		doHandler(ev.fd, 2);
309 	}
310 	private void lowLevelHupHandler(FileHup ev) {
311 		doHandler(ev.fd, 2);
312 	}
313 
314 	/// You can add a file to listen to here. Files can be OS handles or Phobos types. The handlers can be null, meaning use the default
315 	/// (see: setDefaultHandler), or callables with zero or one argument. If they take an argument, it will be the file being handled at this time.
316 	public void addFile(FileType, ReadEventHandler, WriteEventHandler, ErrorEventHandler)
317 		(FileType handle, ReadEventHandler readEventHandler = null, WriteEventHandler writeEventHandler = null, ErrorEventHandler errorEventHandler = null, bool edgeTriggered = true)
318 		if(
319 			// FIXME: we should be able to take other Phobos types too, and correctly translate them up above
320 			templateCheckHelper!(is(FileType == OsFileHandle), "The FileType must be an operating system file handle")
321 			&&
322 			templateCheckHelper!(isValidFileEventDispatcherHandler!(ReadEventHandler, FileType), "The ReadEventHandler was not valid")
323 			&&
324 			templateCheckHelper!(isValidFileEventDispatcherHandler!(WriteEventHandler, FileType), "The WriteEventHandler was not valid")
325 			&&
326 			templateCheckHelper!(isValidFileEventDispatcherHandler!(ErrorEventHandler, FileType), "The ErrorEventHandler was not valid")
327 		)
328 	{
329 		if(!handlersActive)
330 			activateHandlers();
331 
332 		WrappedListener[3] handlerSet;
333 
334 		int events;
335 
336 		if(readEventHandler !is null) {
337 			handlerSet[0] = wrap(readEventHandler);
338 			events |= FileEvents.read;
339 		}
340 		if(writeEventHandler !is null) {
341 			handlerSet[1] = wrap(writeEventHandler);
342 			events |= FileEvents.write;
343 		}
344 		if(errorEventHandler !is null)
345 			handlerSet[2] = wrap(errorEventHandler);
346 
347 		listeners[handle] = handlerSet;
348 
349 
350 		addFileToLoop(handle, events, edgeTriggered);
351 	}
352 
353 	public void removeFile(OsFileHandle handle) {
354 		listeners.remove(handle);
355 		removeFileFromLoopImplementation(handle);
356 	}
357 
358 	/// What should this default handler work on?
359 	public enum HandlerDuty {
360 		read = 0, /// read events
361 		write = 1, /// write events
362 		error = 2, /// error events
363 	}
364 
365 	/// Sets a default handler, used for file events where the custom handler on addFile was null
366 	public void setDefaultHandler(T)(HandlerDuty duty, T handler) if(isValidFileEventDispatcherHandler!(T, OsFileHandle)) {
367 		auto idx = cast(int) duty;
368 
369 		defaultHandlers[idx] = wrap(handler);
370 	}
371 
372 }
373 
374 private FileEventDispatcher fileEventDispatcher;
375 
376 /// To add listeners for file events on a specific file dispatcher, use this.
377 /// See FileEventDispatcher.addFile for the parameters
378 ///
379 /// When you get an event that a file is ready, you MUST read all of it until
380 /// exhausted (that is, read until it would block - you could use select() for
381 /// this or set the file to nonblocking mode) because you only get an event
382 /// when the state changes. Failure to read it all will leave whatever is left
383 /// in the buffer sitting there unnoticed until even more stuff comes in.
384 public void addFileEventListeners(T...)(T t) {// if(__traits(compiles, fileEventDispatcher.addFile(t))) {
385 	fileEventDispatcher.addFile(t);
386 }
387 
388 /// Removes the file from event handling
389 public void removeFileEventListeners(OsFileHandle handle) {
390 	fileEventDispatcher.removeFile(handle);
391 }
392 
393 /// If you add a file to the event loop, which events are you interested in?
394 public enum FileEvents : int {
395 	read = 1, /// the file is ready to be read from
396 	write = 2, /// the file is ready to be written to
397 }
398 
399 /// Adds a file handle to the event loop. When the handle has data available to read
400 /// (if events & FileEvents.read) or write (if events & FileEvents.write), a message
401 /// FileReadyToRead and/or FileReadyToWrite will be dispatched.
402 ///
403 /// note: the file you add should be nonblocking and you should be sure anything in the
404 /// buffers is already handled, since you won't get events for data that already exists
405 
406 // FIXME: do we want to be able to pass a function pointer to be a special handler?
407 public void addFileToLoop(OsFileHandle fd, /* FileEvents */ int events, bool edgeTriggered = true) {
408 	if(insideLoop) {
409 		addFileToLoopImplementation(fd, events, edgeTriggered);
410 	} else {
411 		backFilesForLoop ~= BackFilesForLoop(fd, events, edgeTriggered);
412 	}
413 }
414 
415 // this is so we can add files to the loop before the loop actually exists without the user
416 // needing to know that
417 private struct BackFilesForLoop {
418 	OsFileHandle file;
419 	int events;
420 	bool edgeTriggered;
421 }
422 
423 private BackFilesForLoop[] backFilesForLoop;
424 
425 // Make sure we're caught up on any files added before we started looping
426 private void addBackFilesToLoop() {
427 	assert(insideLoop);
428 	foreach(bf; backFilesForLoop) {
429 		addFileToLoop(bf.file, bf.events, bf.edgeTriggered);
430 	}
431 
432 	backFilesForLoop = null;
433 }
434 
435 /*
436 	addOnIdle(function) is similar to calling setInterval(function, 0)
437 
438 	auto id = setTimeout(function, wait)
439 	clearTimeout(id)
440 
441 	auto id = setInterval(function, call at least after)
442 	clearInterval(0)
443 
444 */
445 
446 private bool insideLoop = false;
447 
448 version(linux) {
449 	void makeNonBlocking(int fd) {
450 		auto flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0);
451 		if(flags == -1)
452 			throw new Exception("fcntl get");
453 		flags |= fcntl.O_NONBLOCK;
454 		auto s = fcntl.fcntl(fd, fcntl.F_SETFL, flags);
455 		if(s == -1)
456 			throw new Exception("fcntl set");
457 	}
458 
459 	int epoll = -1;
460 
461 	private void addFileToLoopImplementation(int fd, int events, bool edgeTriggered = true) @system {
462 		epoll_event ev = void;
463 
464 		ev.events = 0;
465 
466 		// I don't remember why I made it edge triggered in the first
467 		// place as that requires a bit more care to do correctly and I don't
468 		// think I've ever taken that kind of care. I'm going to try switching it
469 		// to level triggered (the event fires whenever the loop goes through and
470 		// there's still data available) and see if things work better.
471 
472 		// OK I'm turning it back on because otherwise unhandled events
473 		// cause an infinite loop. So when an event comes, you MUST starve
474 		// the read to get all your info in a timely fashion. Gonna document this.
475 		if(edgeTriggered)
476 			ev.events = EPOLLET; // edge triggered
477 
478 		// Oh I think I know why I did this: if it is level triggered
479 		// and the data is not actually handled, it infinite loops
480 		// on it. So either way, the application needs to do its thing:
481 		// either consume all available data every single time it is
482 		// triggered - read until you get EAGAIN, OR make sure that
483 		// data is never ignored; that every trigger leads to at LEAST
484 		// ONE read.
485 		//
486 		// With writes, it is important to be extremely careful with
487 		// level triggered - a file is often ready to write, especially
488 		// if you aren't actually using it! I like to do blocking
489 		// writes with non-blocking reads, so any level-triggered epoll
490 		// on write is probably not what I want.
491 		//
492 		// Bottom line is this is a kinda leaky abstraction either way
493 		// and we all need to understand what is going on to make the
494 		// best of it. Also watch your CPU usage for infinite loops!
495 
496 		if(events & FileEvents.read)
497 			ev.events |= EPOLLIN;
498 		if(events & FileEvents.write)
499 			ev.events |= EPOLLOUT;
500 		ev.data.fd = fd;
501 		epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &ev);
502 	}
503 
504 	private void removeFileFromLoopImplementation(int fd) @system {
505 		epoll_event ev = void;
506 		ev.data.fd = fd;
507 		epoll_ctl(epoll, EPOLL_CTL_DEL, fd, &ev);
508 	}
509 
510 
511 	private void loopImplementation() @system {
512 		insideLoop = true;
513 		scope(exit)
514 			insideLoop = false;
515 
516 		epoll = epoll_create1(0);
517 		if(epoll == -1)
518 			throw new Exception("epoll_create1");
519 		scope(exit) {
520 			unix.close(epoll);
521 			epoll = -1;
522 		}
523 
524 		// anything done before the loop is open needs to be caught up on
525 		addBackFilesToLoop();
526 
527 		addFileToLoop(pipes[0], FileEvents.read, false);
528 
529 		epoll_event[16] events = void;
530 
531 		timeval tv;
532 
533 		outer_loop: for(;;) {
534 			int lowestWait = -1; /* wait forever. this is in milliseconds */
535 			if(timers.length) {
536 				gettimeofday(&tv, null);
537 				lowestWait = timers[0].timeoutRemaining;
538 			}
539 
540 			auto nfds = epoll_wait(epoll, events.ptr, events.length, lowestWait);
541 			moreEvents:
542 			if(nfds == -1) {
543 				if(errno == EINTR) {
544 					// if we're interrupted, we can just advance the timers (we know none triggered since the timeout didn't go off) and try again
545 					if(timers.length) {
546 						long prev = tv.tv_sec * 1000 + tv.tv_usec / 1000;
547 						gettimeofday(&tv, null);
548 						long diff = tv.tv_sec * 1000 + tv.tv_usec / 1000 - prev;
549 
550 						for(size_t idx = 0; idx < timers.length; idx++) {
551 							auto timer = timers[idx];
552 							timer.timeoutRemaining -= diff;
553 						}
554 					}
555 
556 					continue;
557 				}
558 
559 				throw new Exception("epoll_wait");
560 			}
561 
562 
563 			foreach(n; 0 .. nfds) {
564 				auto fd = events[n].data.fd;
565 
566 				if(fd == pipes[0]) {
567 					if(readFromEventPipe() == false)
568 						break outer_loop;
569 				} else {
570 					auto flags = events[n].events;
571 					import core.stdc.stdio;
572 					if(flags & EPOLLIN) {
573 						sendSync(FileReadyToRead(fd));
574 					}
575 					if(flags & EPOLLOUT) {
576 						sendSync(FileReadyToWrite(fd));
577 					}
578 					if((flags & EPOLLERR)) {
579 						//import core.stdc.stdio; printf("ERROR on fd from epoll %d\n", fd);
580 						sendSync(FileError(fd));
581 
582 						// I automatically remove them because otherwise the error flag
583 						// may never actually be cleared and this thing will infinite loop.
584 						removeFileEventListeners(fd);
585 					}
586 					if((flags & EPOLLHUP)) {
587 						//import core.stdc.stdio; printf("HUP on fd from epoll %d\n", fd);
588 						sendSync(FileHup(fd));
589 					}
590 				}
591 			}
592 
593 			// are any timers ready to fire?
594 			if(timers.length) {
595 				long prev = tv.tv_sec * 1000 + tv.tv_usec / 1000;
596 				gettimeofday(&tv, null);
597 				long diff = tv.tv_sec * 1000 + tv.tv_usec / 1000 - prev;
598 
599 				bool resetDone = false;
600 				for(size_t idx = 0; idx < timers.length; idx++) {
601 					auto timer = timers[idx];
602 					timer.timeoutRemaining -= diff;
603 					if(timer.timeoutRemaining <= 0) {
604 						if(timer.countRemaining) {
605 							timer.countRemaining--;
606 							if(timer.countRemaining != 0)
607 								goto reset;
608 							// otherwise we should remove it
609 							for(size_t i2 = idx; i2 < timers.length - 1; i2++) {
610 								timers[i2] = timers[i2 + 1];
611 							}
612 
613 							timers.length = timers.length - 1;
614 							idx--; // cuz we removed it, this keeps the outer loop going
615 						} else {
616 							reset:
617 							timer.timeoutRemaining += timer.originalTimeout;
618 							// this is meant to throttle - if we missed a frame, oh well, just skip it instead of trying to throttle
619 							// FIXME: maybe the throttling should be configurable
620 							if(timer.timeoutRemaining <= 0)
621 								timer.timeoutRemaining = timer.originalTimeout;
622 							resetDone = true;
623 						}
624 						timer.handler.call(null);
625 					}
626 				}
627 
628 				if(resetDone) {
629 					// it could be out of order now, so we'll resort
630 					import std.algorithm;
631 					import std.range;
632 					timers = sort!("a.timeoutRemaining < b.timeoutRemaining")(timers).array;
633 				}
634 			}
635 
636 			nfds = epoll_wait(epoll, events.ptr, events.length, 0 /* no wait */);
637 			if(nfds != 0)
638 				goto moreEvents;
639 
640 			// no immediate events means we're idle for now, run those functions
641 			foreach(idleHandler; idleHandlers)
642 				idleHandler.call(null);
643 		}
644 	}
645 }
646 
647 private bool readFromEventPipe() {
648 	hash_t hash;
649 	void* ptr;
650 
651 	ubyte[hash.sizeof + ptr.sizeof] buffer;
652 
653 	for(;;) {
654 		auto read = unix.read(pipes[0], buffer.ptr, buffer.length);
655 		if(read == -1) {
656 			if(errno == EAGAIN) {
657 				break; // we got it all
658 			}
659 			throw new Exception("read");
660 		} else if(read == 0) {
661 			assert(0); // this is never supposed to happen
662 		} else {
663 			assert(read == buffer.length);
664 
665 			hash = * cast(hash_t*)(cast(void*) (buffer[0 .. hash_t.sizeof]));
666 			ptr  = * cast(void** )(cast(void*) (buffer[hash_t.sizeof .. hash_t.sizeof + (void*).sizeof]));
667 
668 			if(hash == 0 && ptr is null)
669 				return false;
670 
671 			dispatchToListenerWithPtr(hash, ptr);
672 			free(ptr);
673 		}
674 	}
675 	return true;
676 }
677 
678 private interface WrappedListener {
679 	// to call the function...
680 	void call(void* ptr);
681 
682 	// and this checks if it matches a given callable, used for removing listeners
683 	bool matches(void*[2] pair);
684 }
685 
686 private WrappedListener wrap(T)(T t) {
687 	static if(is(T == typeof(null)))
688 		return null;
689 	else {
690 		return new class WrappedListener {
691 			override void call(void* ptr) {
692 				enum arity = ParameterTypeTuple!(T).length;
693 				static if(arity == 1)
694 					t(*(cast(ParameterTypeTuple!(T)[0]*) ptr));
695 				else static if(arity == 0)
696 					t();
697 				else static assert(0, "bad number of arguments");
698 			}
699 
700 			override bool matches(void*[2] pair) {
701 				return pair == getPtrPair(t);
702 			}
703 		};
704 	}
705 }
706 
707 private void*[2] getPtrPair(T)(T t) {
708 	void* funcptr, frameptr;
709 	static if(is(T == delegate)) {
710 		funcptr = cast(void*) t.funcptr;
711 		frameptr = t.ptr;
712 	} else static if(is(T == function)) {
713 		// FIXME: why doesn't it use this branch when given a function?
714 		funcptr = cast(void*) t;
715 		frameptr = null;
716 	} else {
717 		// FIXME: perhaps we should use something else...
718 		funcptr = cast(void*) t;
719 		frameptr = null;
720 	}
721 
722 	return [funcptr, frameptr];
723 }
724 
725 private void dispatchToListenerWithPtr(hash_t hash, void* ptr) {
726 	auto funclist = hash in listeners;
727 	if(funclist is null)
728 		return;
729 	foreach(func; *funclist) {
730 		if(func !is null)
731 			func.call(ptr);
732 	}
733 }
734 
735 import unix = core.sys.posix.unistd;
736 import fcntl = core.sys.posix.fcntl;
737 import core.stdc.errno;
738 alias int OsFileHandle;
739 private int[2] pipes;
740 /// you generally won't want to call this, but if you fork()
741 /// and then try to use the thing without exec(), you might want
742 /// new pipes so the events don't get mixed up.
743 /* private */ void openNewEventPipes() {
744 	unix.pipe(pipes);
745 	makeNonBlocking(pipes[0]);
746 	makeNonBlocking(pipes[1]);
747 }
748 
749 // FIXME: maybe I should reset all the handles too when new thigns are opened
750 // so like listeners = null, etc.
751 
752 // you shouldn't have to call this
753 void closeEventPipes() {
754 	unix.close(pipes[0]);
755 	unix.close(pipes[1]);
756 
757 	pipes[0] = -1;
758 	pipes[1] = -1;
759 }
760 
761 static this() {
762 	openNewEventPipes();
763 }
764 
765 /* **** */
766 // system events
767 
768 // FIXME: we probably want some kind of mid level events that dispatch based on file handle too; a better addFileToLoop might have delegates for each type of event right then and there. But this should not be required because such might be too fat and slow for certain applications
769 
770 /// This is a low level event that is dispatched when a listened file (see: addFileToLoop) is ready to be read
771 /// You should read as much as possible without blocking from the file now, as a future event may not be fired for left over data
772 struct FileReadyToRead {
773 	OsFileHandle fd; // file handle
774 }
775 
776 /// This is a low level event that is dispatched when a listened file (see: addFileToLoop) is ready to be written to
777 struct FileReadyToWrite {
778 	OsFileHandle fd; // file handle;
779 }
780 
781 /// This is a low level event that is dispatched when a listened file (see: addFileToLoop) has an error
782 struct FileError {
783 	OsFileHandle fd; // file handle;
784 }
785 
786 /// This is a low level event that is dispatched when a listened file (see: addFileToLoop) has a hang up event
787 struct FileHup {
788 	OsFileHandle fd; // file handle;
789 }
790 
791 /* **** */
792 // epoll
793 
794 version(linux) {
795 	import core.sys.linux.epoll;
796 	import core.sys.posix.sys.time;
797 }
798 
799 /* **** */
800 // test program
801 
802 struct Test {}
803 import std.stdio;
804 
805 void listenInt(int a) {
806 	writeln("here lol");
807 }
808 
809 version(eventloop_demo)
810 void main() {
811 /*
812 	addFileToLoop(0, FileEvents.read); // add stdin data to our event loop
813 
814 	addListener((FileReadyToRead fr) {
815 		ubyte[100] buffer;
816 		auto got = unix.read(0, buffer.ptr, buffer.length);
817 		if(got == -1)
818 			throw new Exception("wtf");
819 		if(got == 0)
820 			exit;
821 		else
822 			writeln(fr.fd, " sent ", cast(string) buffer[0 .. got]);
823 	});
824 */
825 	FileEventDispatcher dispatcher;
826 
827 	dispatcher.addFile(0, (int fd) {
828 		ubyte[100] buffer;
829 		auto got = unix.read(fd, buffer.ptr, buffer.length);
830 		if(got == -1)
831 			throw new Exception("wtf");
832 		if(got == 0)
833 			exit;
834 		else
835 			writeln(fd, " sent ", cast(string) buffer[0 .. got]);
836 	}, null, null);
837 
838 	addListener(&listenInt);
839 	sendSync(10);
840 	removeListener(&listenInt);
841 	addListener(delegate void(int a) { writeln("got ", a); });
842 	addListener(delegate void(File a) { writeln("got ", a); });
843 	send(20);
844 	send(stdin);
845 
846 	loop();
847 }
848 
849 /* **** */
850 // hash function
851 
852 // the following is copy/pasted from druntime src/rt/util/hash.d
853 // is that available as an import somewhere in the stdlib?
854 
855 
856 version( X86 )
857     version = AnyX86;
858 version( X86_64 )
859     version = AnyX86;
860 version( AnyX86 )
861     version = HasUnalignedOps;
862 
863 
864 @trusted pure nothrow
865 hash_t hashOf( const (void)* buf, size_t len, hash_t seed = 0 )
866 {
867     /*
868      * This is Paul Hsieh's SuperFastHash algorithm, described here:
869      *   http://www.azillionmonkeys.com/qed/hash.html
870      * It is protected by the following open source license:
871      *   http://www.azillionmonkeys.com/qed/weblicense.html
872      */
873     static uint get16bits( const (ubyte)* x ) pure nothrow
874     {
875         // CTFE doesn't support casting ubyte* -> ushort*, so revert to
876         // per-byte access when in CTFE.
877         version( HasUnalignedOps )
878         {
879             if (!__ctfe)
880                 return *cast(ushort*) x;
881         }
882 
883         return ((cast(uint) x[1]) << 8) + (cast(uint) x[0]);
884     }
885 
886     // NOTE: SuperFastHash normally starts with a zero hash value.  The seed
887     //       value was incorporated to allow chaining.
888     auto data = cast(const (ubyte)*) buf;
889     auto hash = seed;
890     int  rem;
891 
892     if( len <= 0 || data is null )
893         return 0;
894 
895     rem = len & 3;
896     len >>= 2;
897 
898     for( ; len > 0; len-- )
899     {
900         hash += get16bits( data );
901         auto tmp = (get16bits( data + 2 ) << 11) ^ hash;
902         hash  = (hash << 16) ^ tmp;
903         data += 2 * ushort.sizeof;
904         hash += hash >> 11;
905     }
906 
907     switch( rem )
908     {
909     case 3: hash += get16bits( data );
910             hash ^= hash << 16;
911             hash ^= data[ushort.sizeof] << 18;
912             hash += hash >> 11;
913             break;
914     case 2: hash += get16bits( data );
915             hash ^= hash << 11;
916             hash += hash >> 17;
917             break;
918     case 1: hash += *data;
919             hash ^= hash << 10;
920             hash += hash >> 1;
921             break;
922      default:
923             break;
924     }
925 
926     /* Force "avalanching" of final 127 bits */
927     hash ^= hash << 3;
928     hash += hash >> 5;
929     hash ^= hash << 4;
930     hash += hash >> 17;
931     hash ^= hash << 25;
932     hash += hash >> 6;
933 
934     return hash;
935 }
936 
937