From e28450f9f5b9a80c3f5a19b7078307b95c3c1245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Tue, 19 May 2020 10:42:25 +0200 Subject: [PATCH] Implement a CFRunLoop based event loop. This enables efficient integration of the kqueue based I/O processing with Apple OS based UI apps. On top of that, an FSEvent based directory watcher can now be implemented to replace the inefficient generic watcher that is used on macOS right now. --- dub.sdl | 6 ++ source/eventcore/core.d | 4 +- .../eventcore/drivers/posix/loop/cfrunloop.d | 65 +++++++++++++++++++ source/eventcore/internal/corefoundation.d | 35 ++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 source/eventcore/drivers/posix/loop/cfrunloop.d create mode 100644 source/eventcore/internal/corefoundation.d diff --git a/dub.sdl b/dub.sdl index 5abd12e..2217d0c 100644 --- a/dub.sdl +++ b/dub.sdl @@ -23,6 +23,12 @@ configuration "epoll-gaia" { versions "EventcoreEpollDriver" } +configuration "cfrunloop" { + platforms "osx" + versions "EventcoreCFRunLoopDriver" + lflags "-framework" "CoreFoundation" +} + configuration "kqueue" { platforms "osx" "freebsd" versions "EventcoreKqueueDriver" diff --git a/source/eventcore/core.d b/source/eventcore/core.d index 44d8754..c1447ba 100644 --- a/source/eventcore/core.d +++ b/source/eventcore/core.d @@ -2,14 +2,16 @@ module eventcore.core; public import eventcore.driver; -import eventcore.drivers.posix.loop.select; +import eventcore.drivers.posix.loop.cfrunloop; import eventcore.drivers.posix.loop.epoll; import eventcore.drivers.posix.loop.kqueue; +import eventcore.drivers.posix.loop.select; import eventcore.drivers.libasync; import eventcore.drivers.winapi.driver; import eventcore.internal.utils : mallocT, freeT; version (EventcoreEpollDriver) alias NativeEventDriver = EpollEventDriver; +else version (EventcoreCFRunLoopDriver) alias NativeEventDriver = CFRunLoopEventDriver; else version (EventcoreKqueueDriver) alias NativeEventDriver = KqueueEventDriver; else version (EventcoreWinAPIDriver) alias NativeEventDriver = WinAPIEventDriver; else version (EventcoreLibasyncDriver) alias NativeEventDriver = LibasyncEventDriver; diff --git a/source/eventcore/drivers/posix/loop/cfrunloop.d b/source/eventcore/drivers/posix/loop/cfrunloop.d new file mode 100644 index 0000000..9dca204 --- /dev/null +++ b/source/eventcore/drivers/posix/loop/cfrunloop.d @@ -0,0 +1,65 @@ +/** + `CFRunLoop` based event loop for macOS UI compatible operation. +*/ +module eventcore.drivers.posix.loop.cfrunloop; +@safe: /*@nogc:*/ nothrow: + +version (EventcoreCFRunLoopDriver): + +import eventcore.drivers.posix.loop.kqueue; +import eventcore.internal.corefoundation; +import eventcore.internal.utils; +import core.time; + + +alias CFRunLoopEventDriver = PosixEventDriver!CFRunLoopEventLoop; + +final class CFRunLoopEventLoop : KqueueEventLoopBase { +@safe nothrow: + private { + CFFileDescriptorRef m_kqueueDescriptor; + CFRunLoopSourceRef m_kqueueSource; + } + + this() + @trusted @nogc { + super(); + + CFFileDescriptorContext ctx; + ctx.info = cast(void*)this; + + m_kqueueDescriptor = CFFileDescriptorCreate(kCFAllocatorDefault, + m_queue, false, &processKqueue, &ctx); + + CFFileDescriptorEnableCallBacks(m_kqueueDescriptor, CFOptionFlags.kCFFileDescriptorReadCallBack); + m_kqueueSource = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, m_kqueueDescriptor, 0); + CFRunLoopAddSource(CFRunLoopGetMain(), m_kqueueSource, kCFRunLoopDefaultMode); + } + + override bool doProcessEvents(Duration timeout) + @trusted { + // submit changes and process pending events + auto kres = doProcessEventsBase(0.seconds); + + CFTimeInterval to = kres ? 0.0 : 1e-7 * timeout.total!"hnsecs"; + auto res = CFRunLoopRunInMode(kCFRunLoopDefaultMode, to, true); + return kres || res == CFRunLoopRunResult.kCFRunLoopRunHandledSource; + } + + override void dispose() + { + () @trusted { + CFRelease(m_kqueueSource); + CFRelease(m_kqueueDescriptor); + } (); + super.dispose(); + } + + private static extern(C) void processKqueue(CFFileDescriptorRef fdref, + CFOptionFlags callBackTypes, void* info) + { + auto this_ = () @trusted { return cast(CFRunLoopEventLoop)info; } (); + auto res = this_.doProcessEventsBase(0.seconds); + () @trusted { CFFileDescriptorEnableCallBacks(this_.m_kqueueDescriptor, CFOptionFlags.kCFFileDescriptorReadCallBack); } (); + } +} diff --git a/source/eventcore/internal/corefoundation.d b/source/eventcore/internal/corefoundation.d new file mode 100644 index 0000000..5545da0 --- /dev/null +++ b/source/eventcore/internal/corefoundation.d @@ -0,0 +1,35 @@ +module eventcore.internal.corefoundation; + +version (Darwin): + +extern(C): + +static if (!is(typeof(CFRelease))) { + alias CFTypeRef = const(void)*; + alias CFTypeRef CFAllocatorRef; + extern const CFAllocatorRef kCFAllocatorDefault; + CFTypeRef CFRetain(CFTypeRef cf); + void CFRelease(CFTypeRef cf); +} + +static if (!is(typeof(CFRunLoop))) { + alias CFRunLoopMode = CFStringRef; + struct __CFRunLoop; + alias CFRunLoopRef = __CFRunLoop*; + struct __CFRunLoopSource; + alias CFRunLoopSourceRef = __CFRunLoopSource*; + + alias CFTimeInterval = double; + alias Boolean = bool; + + extern const CFStringRef kCFRunLoopDefaultMode; + extern const CFStringRef kCFRunLoopCommonModes; + + void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode); + CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled); +} + +static if (!is(CFFileDescriptor)) { + alias FSEventStreamRef = x; + FSEventStreamRef FSEventStreamCreate(CFAllocatorRef allocator, FSEventStreamCallback callback, FSEventStreamContext *context, CFArrayRef pathsToWatch, FSEventStreamEventId sinceWhen, CFTimeInterval latency, FSEventStreamCreateFlags flags); +}