1 /++ 2 + Authors: Stephan Dilly, lastname dot firstname at gmail dot com 3 + Copyright: MIT 4 +/ 5 module unecht.core.fibers; 6 7 public import core.thread : Fiber; 8 import core.thread : Thread; 9 10 import derelict.util.system; 11 12 import std.datetime : Duration; 13 14 /// function type that can be used as a fiber 15 alias UEFiberFunc = void function(); 16 /// delegate type that can be used as a fiber 17 alias UEFiberDelegate = void delegate(); 18 19 /++ 20 + acts like a std.thread.Fiber - adds child Fiber member to enable yield on child fibers (=wait for child fiber to finish) 21 + 22 + See_Also: 23 + `UEFibers` 24 +/ 25 final class UEFiber : Fiber 26 { 27 /// pointer to child Fiber 28 private Fiber child; 29 30 //TODO: make nothrow once we loose the dmd<2067 compat 31 /// c'tor 32 public this(T)(T fn) if (is(T == UEFiberFunc) || is(T == UEFiberDelegate)) 33 { 34 super(fn); 35 36 initParent(); 37 } 38 39 /// resets the Fiber to be reused with another method 40 public void reset(T)(T fn) if (is(T == UEFiberFunc) || is(T == UEFiberDelegate)) 41 { 42 super.reset(fn); 43 44 initParent(); 45 } 46 47 /// usage of `reset` 48 @system unittest 49 { 50 bool res; 51 auto fiber = new UEFiber(cast(UEFiberDelegate) { res = false; }); 52 fiber.reset(cast(UEFiberDelegate) { res = true; }); 53 fiber.call(); 54 assert(res); 55 } 56 57 /// initializes parents child property to point to this 58 private void initParent() 59 { 60 UEFiber parent = cast(UEFiber) Fiber.getThis(); 61 if (parent) 62 { 63 assert(parent.child is null); 64 parent.child = this; 65 } 66 } 67 68 /// will call the Fiber in a nothrow way, so it will return any thrown exception inside without automatic rethrow 69 public void safeCall() 70 { 71 static if (__VERSION__ >= 2067) 72 auto e = call(Fiber.Rethrow.no); 73 else 74 auto e = call(false); 75 76 if (e) 77 { 78 static if (__VERSION__ < 2067) 79 { 80 import std.stdio : writefln; 81 82 writefln("error: %s", e); 83 } 84 else 85 { 86 import std.experimental.logger : errorf; 87 88 errorf("error: %s", e.toString()); 89 } 90 } 91 } 92 } 93 94 /// 95 @system unittest 96 { 97 int i = 0; 98 auto fiber = new UEFiber(cast(UEFiberDelegate) { i++; Fiber.yield; i++; }); 99 100 fiber.call(); // executes until yield 101 assert(i == 1); 102 103 fiber.call(); // finishes 104 assert(i == 2); 105 } 106 107 /++ 108 + returns a function object that can be used to wait in a `UEFiber` for a certain amount of time 109 + 110 + Params: 111 + d = a string that will be mixed in and has to evaluate to a `DateTime` 112 + 113 + Returns: A function object 114 + 115 + See_Also: 116 + `UEFiber`, `UEFiberFunc` 117 + 118 + Examples: 119 + --- 120 + UEFibers.yield(waitFiber!"2.seconds"); 121 + --- 122 +/ 123 UEFiberFunc waitFiber(string d)() 124 { 125 import std.datetime : Clock, minutes, msecs, nsecs, seconds; 126 127 return { 128 auto targetTime = Clock.currTime + mixin(d); 129 while (Clock.currTime < targetTime) 130 Fiber.yield; 131 }; 132 } 133 134 /++ 135 + UEFibers acts as a container for fibers. 136 + It manages reusing `UEFiber` objects after they are finished. 137 + `startFiber` first tries to find a finished `UEFiber` and only otherwise allocates 138 + 139 + See_Also: 140 + `UEFiber` 141 +/ 142 struct UEFibers 143 { 144 private static UEFiber[] fibers; 145 146 /// start a function as a fiber 147 public static void startFiber(UEFiberFunc func) 148 { 149 import std.functional : toDelegate; 150 151 startFiber(func.toDelegate()); 152 } 153 154 /// ditto 155 public static void startFiber(UEFiberDelegate func) 156 { 157 UEFiber newFiber = findFreeFiber(); 158 159 if (newFiber) 160 { 161 newFiber.reset(func); 162 } 163 else 164 { 165 newFiber = new UEFiber(func); 166 fibers ~= newFiber; 167 } 168 169 newFiber.safeCall(); 170 } 171 172 //TODO: when runFibers moves TERM'd fibers to end then iterate over array in reverse 173 private static UEFiber findFreeFiber() 174 { 175 foreach (f; fibers) 176 { 177 if (f.state == Fiber.State.TERM) 178 return f; 179 } 180 181 return null; 182 } 183 184 /// yield the current fiber until func is finished running 185 public static yield(UEFiberDelegate func) 186 { 187 assert(Fiber.getThis()); 188 189 startFiber(func); 190 191 Fiber.yield(); 192 } 193 194 /// ditto 195 public static yield(UEFiberFunc func) 196 { 197 import std.functional : toDelegate; 198 199 yield(func.toDelegate()); 200 } 201 202 //TODO: move Fibers in TERM state to the end of array 203 /++ 204 + Calls all running fibers that do not wait for a child to finish 205 + 206 + Note: Do not count on the order of execution of fibers, they could be reordered 207 +/ 208 public static void runFibers() 209 { 210 foreach (f; fibers) 211 { 212 if (f.state != Fiber.State.TERM) 213 { 214 if (!f.child) 215 { 216 f.safeCall(); 217 } 218 else 219 { 220 if (f.child.state == Fiber.State.TERM) 221 { 222 f.child = null; 223 f.safeCall(); 224 } 225 } 226 } 227 } 228 } 229 } 230 231 /// basic yield 232 @system unittest 233 { 234 int i = 0; 235 // reset 236 UEFibers.fibers.length = 0; 237 238 UEFibers.startFiber({ i++; Fiber.yield(); i++; }); 239 240 assert(i == 1); 241 UEFibers.runFibers(); 242 assert(i == 2); 243 } 244 245 /// fiber object reuse 246 @system unittest 247 { 248 int i = 0; 249 // reset 250 UEFibers.fibers.length = 0; 251 252 UEFibers.startFiber({ i++; }); 253 UEFibers.startFiber({ i++; }); 254 255 assert(UEFibers.fibers.length == 1); 256 assert(i == 2); 257 } 258 259 @system unittest 260 { 261 // test yield on other fibers 262 263 string log; 264 265 UEFibers.fibers.length = 0; 266 267 auto f1 = { log ~= 'b'; Fiber.yield(); log ~= 'c'; }; 268 269 UEFibers.startFiber({ log ~= 'a'; UEFibers.yield(f1); log ~= 'd'; }); 270 271 assert(UEFibers.fibers.length == 2); 272 assert(log == "ab", log); 273 274 UEFibers.runFibers(); 275 276 assert(log == "abc"); 277 278 UEFibers.runFibers(); 279 280 assert(log == "abcd"); 281 282 UEFibers.startFiber({ log ~= 'e'; }); 283 284 assert(log == "abcde"); 285 286 assert(UEFibers.fibers.length == 2); 287 } 288 289 @system unittest 290 { 291 // test reusing fibers 292 293 int i; 294 295 UEFibers.fibers.length = 0; 296 297 auto f1 = { Fiber.yield(); i++; }; 298 299 foreach (j; 0 .. 5) 300 UEFibers.startFiber(f1); 301 302 assert(UEFibers.fibers.length == 5); 303 assert(i == 0); 304 305 UEFibers.runFibers(); 306 307 assert(i == 5); 308 309 foreach (j; 0 .. 5) 310 UEFibers.startFiber(f1); 311 312 assert(UEFibers.fibers.length == 5); 313 assert(i == 5); 314 315 UEFibers.runFibers(); 316 317 assert(i == 10); 318 319 assert(UEFibers.fibers.length == 5); 320 } 321 322 @system unittest 323 { 324 // test wait fiber 325 326 import std.datetime : Clock, msecs; 327 328 UEFibers.fibers.length = 0; 329 bool run = false; 330 331 immutable now = Clock.currTime; 332 UEFibers.startFiber({ UEFibers.yield(waitFiber!"1.msecs"); run = true; }); 333 334 int cycles = 0; 335 while (!run) 336 { 337 UEFibers.runFibers(); 338 cycles++; 339 } 340 341 assert(Clock.currTime - now >= 1.msecs); 342 assert(cycles > 10, "this should at least take 10 cylces"); 343 } 344 345 @system unittest 346 { 347 // test bug about resetting existing fibers 348 349 import std.datetime : Clock, msecs; 350 351 UEFibers.fibers.length = 0; 352 auto run = 0; 353 354 immutable now = Clock.currTime; 355 UEFibers.startFiber({ 356 foreach (i; 0 .. 5) 357 { 358 UEFibers.yield(waitFiber!"1.msecs"); 359 360 run++; 361 } 362 }); 363 364 while (run < 5) 365 { 366 UEFibers.runFibers(); 367 } 368 369 assert(UEFibers.fibers.length == 2); 370 assert(Clock.currTime - now >= 5.msecs); 371 }