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