1 module dorm.lib.util;
2 
3 import core.sync.event;
4 import std.algorithm;
5 import std.functional;
6 import std.traits;
7 import std.typecons;
8 
9 import dorm.lib.ffi;
10 
11 /// Library-agnostic helper that's basically an Event. Exposes
12 /// - `set`
13 /// - `wait`
14 /// - `reset`
15 struct Awaiter
16 {
17 	version (Have_vibe_core)
18 	{
19 		import vibe.core.sync;
20 
21 		shared(ManualEvent) event;
22 		int emitCount;
23 
24 		void set() nothrow
25 		{
26 			event.emit();
27 		}
28 
29 		void wait() nothrow
30 		{
31 			event.waitUninterruptible(emitCount);
32 		}
33 
34 		void reset() nothrow @safe
35 		{
36 			emitCount = event.emitCount;
37 		}
38 
39 		static Awaiter make() @trusted
40 		{
41 			auto ret = Awaiter(createSharedManualEvent);
42 			ret.emitCount = ret.event.emitCount;
43 			return ret;
44 		}
45 	}
46 	else
47 	{
48 		Event event;
49 		alias event this;
50 
51 		static Awaiter make() @trusted
52 		{
53 			return Awaiter(Event(true, false));
54 		}
55 	}
56 }
57 
58 struct FreeableAsyncResult(T)
59 {
60 	Awaiter awaiter;
61 	static if (is(T : void delegate(scope U value), U))
62 		T forward_callback;
63 	else static if (!is(T == void))
64 		T raw_result;
65 	Exception error;
66 
67 	@disable this();
68 
69 	this(Awaiter awaiter) @trusted
70 	{
71 		this.awaiter = move(awaiter);
72 	}
73 
74 	static FreeableAsyncResult make() @trusted
75 	{
76 		return FreeableAsyncResult(Awaiter.make);
77 	}
78 
79 	static if (is(T == void))
80 		alias Callback = extern(C) void function(void* data, scope RormError error) nothrow;
81 	else static if (is(T : void delegate(scope V value), V))
82 		alias Callback = extern(C) void function(void* data, scope V result, scope RormError error) nothrow;
83 	else static if (__traits(isPOD, T) || is(T == P*, P))
84 		alias Callback = extern(C) void function(void* data, T result, scope RormError error) nothrow;
85 	else static assert(false, "Unsupported async type " ~ T.stringof);
86 
87 	Tuple!(Callback, void*) callback() return @safe
88 	{
89 		static if (is(T == void))
90 		{
91 			extern(C) static void ret(void* data, scope RormError error) nothrow
92 			{
93 				static if (DormFFITrace)
94 					debug dormTraceCallback(error);
95 
96 				auto res = cast(FreeableAsyncResult*)data;
97 				if (error)
98 					res.error = error.makeException;
99 				res.awaiter.set();
100 			}
101 		}
102 		else static if (is(T : void delegate(scope U value), U))
103 		{
104 			extern(C) static void ret(void* data, scope U result, scope RormError error) nothrow
105 			{
106 				static if (DormFFITrace)
107 					debug dormTraceCallback(result, error);
108 
109 				auto res = cast(FreeableAsyncResult*)data;
110 				if (error)
111 					res.error = error.makeException;
112 				else
113 				{
114 					try
115 					{
116 						res.forward_callback(result);
117 					}
118 					catch (Exception e)
119 					{
120 						res.error = e;
121 					}
122 				}
123 				res.awaiter.set();
124 			}
125 		}
126 		else
127 		{
128 			extern(C) static void ret(void* data, T result, scope RormError error) nothrow
129 			{
130 				static if (DormFFITrace)
131 					debug dormTraceCallback(result, error);
132 
133 				auto res = cast(FreeableAsyncResult*)data;
134 				if (error)
135 					res.error = error.makeException;
136 				else
137 					res.raw_result = result;
138 				res.awaiter.set();
139 			}
140 		}
141 
142 		return tuple(&ret, cast(void*)&this);
143 	}
144 
145 	void waitAndThrow() @trusted
146 	{
147 		awaiter.wait();
148 		if (error)
149 			throw error;
150 	}
151 
152 	auto result() @safe
153 	{
154 		waitAndThrow();
155 		static if (!is(T == void)
156 			&& !is(T : void delegate(scope U value), U))
157 			return raw_result;
158 	}
159 
160 	void reset() @safe
161 	{
162 		(() @trusted => awaiter.reset())();
163 		static if (!is(T == void)
164 			&& !is(T : void delegate(scope U value), U))
165 			raw_result = T.init;
166 		error = null;
167 	}
168 }
169 
170 auto sync_call(alias fn)(Parameters!fn[0 .. $ - 2] args) @trusted
171 {
172 	static assert(Parameters!(Parameters!fn[$ - 2]).length == 3
173 		|| Parameters!(Parameters!fn[$ - 2]).length == 2);
174 	static assert(is(Parameters!(Parameters!fn[$ - 2])[0] == void*));
175 	static assert(is(Parameters!(Parameters!fn[$ - 2])[$ - 1] == RormError));
176 
177 	enum isVoid = Parameters!(Parameters!fn[$ - 2]).length == 2;
178 
179 	struct Result
180 	{
181 		Exception exception;
182 		static if (!isVoid)
183 			Parameters!(Parameters!fn[$ - 2])[1] ret;
184 		bool sync;
185 	}
186 
187 	Result result;
188 
189 	extern(C) static void callback(Parameters!(Parameters!fn[$ - 2]) args) nothrow
190 	{
191 		static if (DormFFITrace)
192 			debug dormTraceSyncCallback(args[1 .. $]);
193 
194 		auto result = cast(Result*)(args[0]);
195 		static if (!isVoid)
196 			auto data = args[1];
197 		auto error = args[$ - 1];
198 		if (error) result.exception = error.makeException;
199 		else {
200 			static if (!isVoid)
201 				result.ret = data;
202 		}
203 		result.sync = true;
204 	}
205 	fn(forward!args, &callback, &result);
206 	assert(result.sync, "called sync_call with function that does not call its callback in synchronous context!");
207 
208 	if (result.exception)
209 		throw result.exception;
210 
211 	static if (!isVoid)
212 		return result.ret;
213 }
214 
215 template ffiInto(To)
216 {
217 	To ffiInto(From)(From v)
218 	{
219 		static assert(From.tupleof.length == To.tupleof.length,
220 			"FFI member fields count mismatch between "
221 			~ From.stringof ~ " and " ~ To.stringof);
222 
223 		To ret;
224 		foreach (i, ref field; ret.tupleof)
225 		{
226 			static if (is(typeof(field) == FFIArray!T, T))
227 				field = FFIArray!T.fromData(v.tupleof[i]);
228 			else
229 				field = v.tupleof[i];
230 		}
231 		return ret;
232 	}
233 }