1 /** 2 `CFRunLoop` based event loop for macOS UI compatible operation. 3 */ 4 module eventcore.drivers.posix.cfrunloop; 5 @safe: /*@nogc:*/ nothrow: 6 7 version (EventcoreCFRunLoopDriver): 8 9 import eventcore.drivers.posix.kqueue; 10 import eventcore.internal.corefoundation; 11 import eventcore.internal.utils; 12 import core.time; 13 14 15 alias CFRunLoopEventDriver = PosixEventDriver!CFRunLoopEventLoop; 16 17 final class CFRunLoopEventLoop : KqueueEventLoopBase { 18 @safe nothrow: 19 private { 20 CFFileDescriptorRef m_kqueueDescriptor; 21 CFRunLoopSourceRef m_kqueueSource; 22 } 23 24 this() 25 @trusted @nogc { 26 super(); 27 28 CFFileDescriptorContext ctx; 29 ctx.info = cast(void*)this; 30 31 m_kqueueDescriptor = CFFileDescriptorCreate(kCFAllocatorDefault, 32 m_queue, false, &processKqueue, &ctx); 33 34 m_kqueueSource = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, m_kqueueDescriptor, 0); 35 CFRunLoopAddSource(CFRunLoopGetCurrent(), m_kqueueSource, kCFRunLoopDefaultMode); 36 } 37 38 override bool doProcessEvents(Duration timeout) 39 @trusted { 40 import std.algorithm.comparison : min; 41 42 CFFileDescriptorEnableCallBacks(m_kqueueDescriptor, CFOptionFlags.kCFFileDescriptorReadCallBack); 43 44 // submit changes and process pending events 45 auto kres = doProcessEventsBase(0.seconds); 46 if (kres) timeout = 0.seconds; 47 48 // NOTE: the timeout per CFRunLoopRunInMode call is limited to five 49 // seconds to work around the issue that the kqueue CFFileDescriptor 50 // sometimes does not fire. There seems to be some kind of race- 51 // condition, between the edge-triggered kqueue events and 52 // CFFileDescriptorEnableCallBacks/CFRunLoopRunInMode. 53 // 54 // Even changing the order of calls in processKqueue to first 55 // re-enable the callbacks and *then* process the already pending 56 // events does not help (and is also explicitly discouraged in 57 // Apple's documentation). 58 // 59 // NOTE: In contrast to Apple's documentation, kCFRunLoopRunStopped is 60 // returned not only if the loop is explicitly stopped, but also 61 // after handling events normally. For this reason we treat any 62 // return value other than "timed out" as having processed an event. 63 if (timeout == 0.seconds) { 64 while (true) { 65 auto res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true); 66 if (res == CFRunLoopRunResult.kCFRunLoopRunTimedOut) 67 break; 68 else kres = true; 69 } 70 71 CFFileDescriptorEnableCallBacks(m_kqueueDescriptor, CFOptionFlags.kCFFileDescriptorReadCallBack); 72 kres |= doProcessEventsBase(0.seconds); 73 } else while (timeout > 0.seconds) { 74 auto tol = min(timeout, 5.seconds); 75 timeout -= tol; 76 CFTimeInterval to = 1e-7 * tol.total!"hnsecs"; 77 auto res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, to, true); 78 if (res != CFRunLoopRunResult.kCFRunLoopRunTimedOut) { 79 kres = true; 80 break; 81 } 82 83 CFFileDescriptorEnableCallBacks(m_kqueueDescriptor, CFOptionFlags.kCFFileDescriptorReadCallBack); 84 kres |= doProcessEventsBase(0.seconds); 85 if (kres) break; 86 } 87 88 return kres; 89 } 90 91 override void dispose() 92 { 93 () @trusted { 94 CFRelease(m_kqueueSource); 95 CFRelease(m_kqueueDescriptor); 96 } (); 97 super.dispose(); 98 } 99 100 private static extern(C) void processKqueue(CFFileDescriptorRef fdref, 101 CFOptionFlags callBackTypes, void* info) 102 { 103 auto this_ = () @trusted { return cast(CFRunLoopEventLoop)info; } (); 104 this_.doProcessEventsBase(0.seconds); 105 } 106 }