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 }