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 }