1 module eventcore.drivers.winapi.events; 2 3 version (Windows): 4 5 import eventcore.driver; 6 import eventcore.drivers.winapi.core; 7 import eventcore.internal.win32; 8 import eventcore.internal.consumablequeue; 9 import eventcore.internal.utils : mallocT, freeT, nogc_assert, print; 10 11 12 final class WinAPIEventDriverEvents : EventDriverEvents { 13 @safe: /*@nogc:*/ nothrow: 14 private { 15 static struct Trigger { 16 EventID id; 17 bool notifyAll; 18 } 19 20 static struct EventSlot { 21 uint refCount; 22 ConsumableQueue!EventCallback waiters; 23 } 24 25 WinAPIEventDriverCore m_core; 26 HANDLE m_event; 27 EventSlot[EventID] m_events; 28 CRITICAL_SECTION m_mutex; 29 ConsumableQueue!Trigger m_pending; 30 uint m_idCounter; 31 } 32 33 this(WinAPIEventDriverCore core) 34 @nogc { 35 m_core = core; 36 m_event = () @trusted { return CreateEvent(null, false, false, null); } (); 37 m_pending = mallocT!(ConsumableQueue!Trigger); // FIXME: avoid GC allocation 38 InitializeCriticalSection(&m_mutex); 39 m_core.registerEvent(m_event, &triggerPending); 40 } 41 42 void dispose() 43 @trusted { 44 scope (failure) assert(false); 45 freeT(m_pending); 46 } 47 48 package bool checkForLeakedHandles() 49 @trusted { 50 foreach (evt; m_events.byKey) { 51 print("Warning: Event handles leaked at driver shutdown."); 52 return true; 53 } 54 return false; 55 } 56 57 override EventID create() 58 { 59 auto id = EventID(m_idCounter++, 0); 60 if (id == EventID.invalid) id = EventID(m_idCounter++, 0); 61 m_events[id] = EventSlot(1, new ConsumableQueue!EventCallback); // FIXME: avoid GC allocation 62 return id; 63 } 64 65 override void trigger(EventID event, bool notify_all = true) 66 { 67 if (!isValid(event)) return; 68 69 auto pe = event in m_events; 70 assert(pe !is null, "Invalid event ID passed to triggerEvent."); 71 if (notify_all) { 72 foreach (w; pe.waiters.consume) { 73 m_core.removeWaiter(); 74 w(event); 75 } 76 } else { 77 if (!pe.waiters.empty) { 78 m_core.removeWaiter(); 79 pe.waiters.consumeOne()(event); 80 } 81 } 82 } 83 84 override void trigger(EventID event, bool notify_all = true) shared 85 { 86 import core.atomic : atomicStore; 87 88 () @trusted { 89 auto thisus = cast(WinAPIEventDriverEvents)this; 90 EnterCriticalSection(&thisus.m_mutex); 91 if (thisus.isValid(event)) 92 thisus.m_pending.put(Trigger(event, notify_all)); 93 LeaveCriticalSection(&thisus.m_mutex); 94 SetEvent(thisus.m_event); 95 } (); 96 } 97 98 override void wait(EventID event, EventCallback on_event) 99 { 100 if (!isValid(event)) return; 101 102 m_core.addWaiter(); 103 return m_events[event].waiters.put(on_event); 104 } 105 106 override void cancelWait(EventID event, EventCallback on_event) 107 { 108 import std.algorithm.searching : countUntil; 109 import std.algorithm.mutation : remove; 110 111 if (!isValid(event)) return; 112 113 m_events[event].waiters.removePending(on_event); 114 m_core.removeWaiter(); 115 } 116 117 override bool isValid(EventID handle) 118 const { 119 return (handle in m_events) !is null; 120 } 121 122 override void addRef(EventID descriptor) 123 { 124 if (!isValid(descriptor)) return; 125 126 assert(m_events[descriptor].refCount > 0); 127 m_events[descriptor].refCount++; 128 } 129 130 override bool releaseRef(EventID descriptor) 131 { 132 if (!isValid(descriptor)) return true; 133 134 auto pe = descriptor in m_events; 135 nogc_assert(pe.refCount > 0, "Releasing unreference event."); 136 if (--pe.refCount == 0) { 137 // make sure to not leak any waiter references for pending waits 138 foreach (i; 0 .. pe.waiters.length) 139 m_core.removeWaiter(); 140 141 () @trusted nothrow { 142 scope (failure) assert(false); 143 destroy(pe.waiters); 144 CloseHandle(idToHandle(descriptor)); 145 } (); 146 m_events.remove(descriptor); 147 return false; 148 } 149 return true; 150 } 151 152 protected override void* rawUserData(EventID descriptor, size_t size, DataInitializer initialize, DataInitializer destroy) 153 @system { 154 return m_core.rawUserDataImpl(idToHandle(descriptor), size, initialize, destroy); 155 } 156 157 private void triggerPending() 158 { 159 while (true) { 160 Trigger t; 161 { 162 () @trusted { EnterCriticalSection(&m_mutex); } (); 163 scope (exit) () @trusted { LeaveCriticalSection(&m_mutex); } (); 164 if (m_pending.empty) break; 165 t = m_pending.consumeOne; 166 } 167 168 trigger(t.id, t.notifyAll); 169 } 170 } 171 172 private static HANDLE idToHandle(EventID event) 173 @trusted @nogc { 174 return cast(HANDLE)cast(size_t)event; 175 } 176 }