1 /++ 2 $(PITFALL 3 Please note: the api and behavior of this module is not externally stable at this time. See the documentation on specific functions for details. 4 ) 5 6 Shared core functionality including exception helpers, library loader, event loop, and possibly more. Maybe command line processor and uda helper and some basic shared annotation types. 7 8 I'll probably move the url, websocket, and ssl stuff in here too as they are often shared. Maybe a small internationalization helper type (a hook for external implementation) and COM helpers too. I might move the process helpers out to their own module - even things in here are not considered stable to library users at this time! 9 10 If you use this directly outside the arsd library despite its current instability caveats, you might consider using `static import` since names in here are likely to clash with Phobos if you use them together. `static import` will let you easily disambiguate and avoid name conflict errors if I add more here. Some names even clash deliberately to remind me to avoid some antipatterns inside the arsd modules! 11 12 ## Contributor notes 13 14 arsd.core should be focused on things that enable interoperability primarily and secondarily increased code quality between other, otherwise independent arsd modules. As a foundational library, it is not permitted to import anything outside the druntime `core` namespace, except in templates and examples not normally compiled in. This keeps it independent and avoids transitive dependency spillover to end users while also keeping compile speeds fast. To help keep builds snappy, also avoid significant use of ctfe inside this module. 15 16 On my linux computer, `dmd -unittest -main core.d` takes about a quarter second to run. We do not want this to grow. 17 18 `@safe` compatibility is ok when it isn't too big of a hassle. `@nogc` is a non-goal. I might accept it on some of the trivial functions but if it means changing the logic in any way to support, you will need a compelling argument to justify it. The arsd libs are supposed to be reliable and easy to use. That said, of course, don't be unnecessarily wasteful - if you can easily provide a reliable and easy to use way to let advanced users do their thing without hurting the other cases, let's discuss it. 19 20 If functionality is not needed by multiple existing arsd modules, consider adding a new module instead of adding it to the core. 21 22 Unittests should generally be hidden behind a special version guard so they don't interfere with end user tests. 23 24 History: 25 Added March 2023 (dub v11.0). Several functions were migrated in here at that time, noted individually. Members without a note were added with the module. 26 +/ 27 module arsd.core; 28 29 30 static if(__traits(compiles, () { import core.interpolation; })) { 31 import core.interpolation; 32 33 alias InterpolationHeader = core.interpolation.InterpolationHeader; 34 alias InterpolationFooter = core.interpolation.InterpolationFooter; 35 alias InterpolatedLiteral = core.interpolation.InterpolatedLiteral; 36 alias InterpolatedExpression = core.interpolation.InterpolatedExpression; 37 } else { 38 // polyfill for old versions 39 struct InterpolationHeader {} 40 struct InterpolationFooter {} 41 struct InterpolatedLiteral(string literal) {} 42 struct InterpolatedExpression(string code) {} 43 } 44 45 46 // FIXME: add callbacks on file open for tracing dependencies dynamically 47 48 // see for useful info: https://devblogs.microsoft.com/dotnet/how-async-await-really-works/ 49 50 // see: https://wiki.openssl.org/index.php/Simple_TLS_Server 51 52 // see: When you only want to track changes on a file or directory, be sure to open it using the O_EVTONLY flag. 53 54 ///ArsdUseCustomRuntime is used since other derived work from WebAssembly may be used and thus specified in the CLI 55 version(WebAssembly) version = ArsdUseCustomRuntime; 56 57 // note that kqueue might run an i/o loop on mac, ios, etc. but then NSApp.run on the io thread 58 // but on bsd, you want the kqueue loop in i/o too.... 59 60 version(iOS) 61 { 62 version = EmptyEventLoop; 63 version = EmptyCoreEvent; 64 } 65 version(ArsdUseCustomRuntime) 66 { 67 version = EmptyEventLoop; 68 version = UseStdioWriteln; 69 } 70 else 71 { 72 version = HasFile; 73 version = HasSocket; 74 version = HasThread; 75 version = HasErrno; 76 } 77 78 version(HasThread) 79 { 80 import core.thread; 81 import core..volatile; 82 import core.atomic; 83 import core.time; 84 } 85 else 86 { 87 // polyfill for missing core.time 88 struct Duration { 89 static Duration max() { return Duration(); } 90 } 91 } 92 93 version(OSX) { 94 version(ArsdNoCocoa) 95 enum bool UseCocoa = false; 96 else 97 enum bool UseCocoa = true; 98 } 99 100 version(HasErrno) 101 import core.stdc.errno; 102 103 import core.attribute; 104 static if(!__traits(hasMember, core.attribute, "mustuse")) 105 enum mustuse; 106 107 // FIXME: add an arena allocator? can do task local destruction maybe. 108 109 // the three implementations are windows, epoll, and kqueue 110 version(Windows) { 111 version=Arsd_core_windows; 112 113 // import core.sys.windows.windows; 114 import core.sys.windows.winbase; 115 import core.sys.windows.windef; 116 import core.sys.windows.winnls; 117 import core.sys.windows.winuser; 118 import core.sys.windows.winsock2; 119 120 pragma(lib, "user32"); 121 pragma(lib, "ws2_32"); 122 } else version(linux) { 123 version=Arsd_core_epoll; 124 125 static if(__VERSION__ >= 2098) { 126 version=Arsd_core_has_cloexec; 127 } 128 } else version(FreeBSD) { 129 version=Arsd_core_kqueue; 130 131 import core.sys.freebsd.sys.event; 132 } else version(DragonFlyBSD) { 133 // NOT ACTUALLY TESTED 134 version=Arsd_core_kqueue; 135 136 import core.sys.dragonflybsd.sys.event; 137 } else version(NetBSD) { 138 // NOT ACTUALLY TESTED 139 version=Arsd_core_kqueue; 140 141 import core.sys.netbsd.sys.event; 142 } else version(OpenBSD) { 143 version=Arsd_core_kqueue; 144 145 // THIS FILE DOESN'T ACTUALLY EXIST, WE NEED TO MAKE IT 146 import core.sys.openbsd.sys.event; 147 } else version(OSX) { 148 version=Arsd_core_kqueue; 149 150 import core.sys.darwin.sys.event; 151 152 version(DigitalMars) { 153 version=OSXCocoa; 154 } 155 } 156 157 version(OSXCocoa) 158 enum CocoaAvailable = true; 159 else 160 enum CocoaAvailable = false; 161 162 version(Posix) { 163 import core.sys.posix.signal; 164 import core.sys.posix.unistd; 165 166 import core.sys.posix.sys.un; 167 import core.sys.posix.sys.socket; 168 import core.sys.posix.netinet.in_; 169 } 170 171 // FIXME: the exceptions should actually give some explanatory text too (at least sometimes) 172 173 /+ 174 ========================= 175 GENERAL UTILITY FUNCTIONS 176 ========================= 177 +/ 178 179 // enum stringz : const(char)* { init = null } 180 181 /++ 182 A wrapper around a `const(char)*` to indicate that it is a zero-terminated C string. 183 +/ 184 struct stringz { 185 private const(char)* raw; 186 187 /++ 188 Wraps the given pointer in the struct. Note that it retains a copy of the pointer. 189 +/ 190 this(const(char)* raw) { 191 this.raw = raw; 192 } 193 194 /++ 195 Returns the original raw pointer back out. 196 +/ 197 const(char)* ptr() const { 198 return raw; 199 } 200 201 /++ 202 Borrows a slice of the pointer up to (but not including) the zero terminator. 203 +/ 204 const(char)[] borrow() const { 205 if(raw is null) 206 return null; 207 208 const(char)* p = raw; 209 int length; 210 while(*p++) length++; 211 212 return raw[0 .. length]; 213 } 214 } 215 216 /++ 217 A limited variant to hold just a few types. It is made for the use of packing a small amount of extra data into error messages. 218 +/ 219 /+ 220 * if length and ptr are both 0, it is null 221 * if ptr == 1, length is an integer 222 * if ptr == 2, length is an unsigned integer (suggest printing in hex) 223 * if ptr == 3, length is a combination of flags (suggest printing in binary) 224 * if ptr == 4, length is a unix permission thing (suggest printing in octal) 225 * if ptr == 5, length is a double float 226 * if ptr == 15, length must be 0. this holds an empty, non-null, SSO string. 227 * if ptr >= 16 && < 24, length is reinterpret-casted a small string of length of (ptr & 0x7) + 1 228 * if length == size_t.max, ptr is interpreted as a stringz 229 * if ptr >= 1024, it is a non-null D string or byte array. It is a string if the length high bit is clear, a byte array if it is set. the length is what is left after you mask that out. 230 231 All other ptr values are reserved for future expansion. 232 +/ 233 struct LimitedVariant { 234 235 /++ 236 237 +/ 238 enum Contains { 239 null_, 240 intDecimal, 241 intHex, 242 intBinary, 243 intOctal, 244 double_, 245 emptySso, 246 stringSso, 247 stringz, 248 string, 249 bytes, 250 251 invalid, 252 } 253 254 /++ 255 256 +/ 257 Contains contains() const { 258 auto tag = cast(size_t) ptr; 259 if(ptr is null && length is null) 260 return Contains.null_; 261 else switch(tag) { 262 case 1: return Contains.intDecimal; 263 case 2: return Contains.intHex; 264 case 3: return Contains.intBinary; 265 case 4: return Contains.intOctal; 266 case 5: return Contains.double_; 267 case 15: return length is null ? Contains.emptySso : Contains.invalid; 268 default: 269 if(tag >= 16 && tag < 24) { 270 return Contains.stringSso; 271 } else if(tag >= 1024) { 272 if(cast(size_t) length == size_t.max) 273 return Contains.stringz; 274 else 275 return isHighBitSet ? Contains.bytes : Contains..string; 276 } else { 277 return Contains.invalid; 278 } 279 } 280 } 281 282 /// ditto 283 bool containsInt() const { 284 with(Contains) 285 switch(contains) { 286 case intDecimal, intHex, intBinary, intOctal: 287 return true; 288 default: 289 return false; 290 } 291 } 292 293 /// ditto 294 bool containsString() const { 295 with(Contains) 296 switch(contains) { 297 case null_, emptySso, stringSso, string: 298 // case stringz: 299 return true; 300 default: 301 return false; 302 } 303 } 304 305 /// ditto 306 bool containsDouble() const { 307 with(Contains) 308 switch(contains) { 309 case double_: 310 return true; 311 default: 312 return false; 313 } 314 } 315 316 /// ditto 317 bool containsBytes() const { 318 with(Contains) 319 switch(contains) { 320 case bytes, null_: 321 return true; 322 default: 323 return false; 324 } 325 } 326 327 private const(void)* length; 328 private const(ubyte)* ptr; 329 330 private void Throw() const { 331 throw ArsdException!"LimitedVariant"(cast(size_t) length, cast(size_t) ptr); 332 } 333 334 private bool isHighBitSet() const { 335 return (cast(size_t) length >> (size_t.sizeof * 8 - 1) & 0x1) != 0; 336 } 337 338 /++ 339 getString gets a reference to the string stored internally, see [toString] to get a string representation or whatever is inside. 340 341 +/ 342 const(char)[] getString() const return { 343 with(Contains) 344 switch(contains()) { 345 case null_: 346 return null; 347 case emptySso: 348 return (cast(const(char)*) ptr)[0 .. 0]; // zero length, non-null 349 case stringSso: 350 auto len = ((cast(size_t) ptr) & 0x7) + 1; 351 return (cast(char*) &length)[0 .. len]; 352 case string: 353 return (cast(const(char)*) ptr)[0 .. cast(size_t) length]; 354 default: 355 Throw(); assert(0); 356 } 357 } 358 359 /// ditto 360 long getInt() const { 361 if(containsInt) 362 return cast(long) length; 363 else 364 Throw(); 365 assert(0); 366 } 367 368 /// ditto 369 double getDouble() const { 370 if(containsDouble) 371 return *cast(double*) &length; 372 else 373 Throw(); 374 assert(0); 375 } 376 377 /// ditto 378 const(ubyte)[] getBytes() const { 379 with(Contains) 380 switch(contains()) { 381 case null_: 382 return null; 383 case bytes: 384 return ptr[0 .. (cast(size_t) length) & ((1UL << (size_t.sizeof * 8 - 1)) - 1)]; 385 default: 386 Throw(); assert(0); 387 } 388 } 389 390 /++ 391 392 +/ 393 string toString() const { 394 395 string intHelper(string prefix, int radix) { 396 char[128] buffer; 397 buffer[0 .. prefix.length] = prefix[]; 398 char[] toUse = buffer[prefix.length .. $]; 399 400 auto got = intToString(getInt(), toUse[], IntToStringArgs().withRadix(radix)); 401 402 return buffer[0 .. prefix.length + got.length].idup; 403 } 404 405 with(Contains) 406 final switch(contains()) { 407 case null_: 408 return "<null>"; 409 case intDecimal: 410 return intHelper("", 10); 411 case intHex: 412 return intHelper("0x", 16); 413 case intBinary: 414 return intHelper("0b", 2); 415 case intOctal: 416 return intHelper("0o", 8); 417 case emptySso, stringSso, string: 418 return getString().idup; 419 case bytes: 420 auto b = getBytes(); 421 422 return "<bytes>"; // FIXME 423 424 case double_: 425 assert(0); // FIXME 426 case stringz: 427 assert(0); // FIXME 428 case invalid: 429 return "<invalid>"; 430 } 431 } 432 433 /++ 434 435 +/ 436 this(string s) { 437 ptr = cast(const(ubyte)*) s.ptr; 438 length = cast(void*) s.length; 439 } 440 441 /// ditto 442 this(const(ubyte)[] b) { 443 ptr = cast(const(ubyte)*) b.ptr; 444 length = cast(void*) (b.length | (1UL << (size_t.sizeof * 8 - 1))); 445 } 446 447 /// ditto 448 this(long l, int base = 10) { 449 int tag; 450 switch(base) { 451 case 10: tag = 1; break; 452 case 16: tag = 2; break; 453 case 2: tag = 3; break; 454 case 8: tag = 4; break; 455 default: assert(0, "You passed an invalid base to LimitedVariant"); 456 } 457 ptr = cast(ubyte*) tag; 458 length = cast(void*) l; 459 } 460 461 /// ditto 462 version(none) 463 this(double d) { 464 // this crashes dmd! omg 465 assert(0); 466 // ptr = cast(ubyte*) 15; 467 // length = cast(void*) *cast(size_t*) &d; 468 } 469 } 470 471 unittest { 472 LimitedVariant v = LimitedVariant("foo"); 473 assert(v.containsString()); 474 assert(!v.containsInt()); 475 assert(v.getString() == "foo"); 476 477 LimitedVariant v2 = LimitedVariant(4); 478 assert(v2.containsInt()); 479 assert(!v2.containsString()); 480 assert(v2.getInt() == 4); 481 482 LimitedVariant v3 = LimitedVariant(cast(ubyte[]) [1, 2, 3]); 483 assert(v3.containsBytes()); 484 assert(!v3.containsString()); 485 assert(v3.getBytes() == [1, 2, 3]); 486 } 487 488 /++ 489 This is a dummy type to indicate the end of normal arguments and the beginning of the file/line inferred args. It is meant to ensure you don't accidentally send a string that is interpreted as a filename when it was meant to be a normal argument to the function and trigger the wrong overload. 490 +/ 491 struct ArgSentinel {} 492 493 /++ 494 A trivial wrapper around C's malloc that creates a D slice. It multiples n by T.sizeof and returns the slice of the pointer from 0 to n. 495 496 Please note that the ptr might be null - it is your responsibility to check that, same as normal malloc. Check `ret is null` specifically, since `ret.length` will always be `n`, even if the `malloc` failed. 497 498 Remember to `free` the returned pointer with `core.stdc.stdlib.free(ret.ptr);` 499 500 $(TIP 501 I strongly recommend you simply use the normal garbage collector unless you have a very specific reason not to. 502 ) 503 504 See_Also: 505 [mallocedStringz] 506 +/ 507 T[] mallocSlice(T)(size_t n) { 508 import c = core.stdc.stdlib; 509 510 return (cast(T*) c.malloc(n * T.sizeof))[0 .. n]; 511 } 512 513 /++ 514 Uses C's malloc to allocate a copy of `original` with an attached zero terminator. It may return a slice with a `null` pointer (but non-zero length!) if `malloc` fails and you are responsible for freeing the returned pointer with `core.stdc.stdlib.free(ret.ptr)`. 515 516 $(TIP 517 I strongly recommend you use [CharzBuffer] or Phobos' [std.string.toStringz] instead unless there's a special reason not to. 518 ) 519 520 See_Also: 521 [CharzBuffer] for a generally better alternative. You should only use `mallocedStringz` where `CharzBuffer` cannot be used (e.g. when druntime is not usable or you have no stack space for the temporary buffer). 522 523 [mallocSlice] is the function this function calls, so the notes in its documentation applies here too. 524 +/ 525 char[] mallocedStringz(in char[] original) { 526 auto slice = mallocSlice!char(original.length + 1); 527 if(slice is null) 528 return null; 529 slice[0 .. original.length] = original[]; 530 slice[original.length] = 0; 531 return slice; 532 } 533 534 /++ 535 Basically a `scope class` you can return from a function or embed in another aggregate. 536 +/ 537 struct OwnedClass(Class) { 538 ubyte[__traits(classInstanceSize, Class)] rawData; 539 540 static OwnedClass!Class defaultConstructed() { 541 OwnedClass!Class i = OwnedClass!Class.init; 542 i.initializeRawData(); 543 return i; 544 } 545 546 private void initializeRawData() @trusted { 547 if(!this) 548 rawData[] = cast(ubyte[]) typeid(Class).initializer[]; 549 } 550 551 this(T...)(T t) { 552 initializeRawData(); 553 rawInstance.__ctor(t); 554 } 555 556 bool opCast(T : bool)() @trusted { 557 return !(*(cast(void**) rawData.ptr) is null); 558 } 559 560 @disable this(); 561 @disable this(this); 562 563 Class rawInstance() return @trusted { 564 if(!this) 565 throw new Exception("null"); 566 return cast(Class) rawData.ptr; 567 } 568 569 alias rawInstance this; 570 571 ~this() @trusted { 572 if(this) 573 .destroy(rawInstance()); 574 } 575 } 576 577 // might move RecyclableMemory here 578 579 version(Posix) 580 package(arsd) void makeNonBlocking(int fd) { 581 import core.sys.posix.fcntl; 582 auto flags = fcntl(fd, F_GETFL, 0); 583 if(flags == -1) 584 throw new ErrnoApiException("fcntl get", errno); 585 flags |= O_NONBLOCK; 586 auto s = fcntl(fd, F_SETFL, flags); 587 if(s == -1) 588 throw new ErrnoApiException("fcntl set", errno); 589 } 590 591 version(Posix) 592 package(arsd) void setCloExec(int fd) { 593 import core.sys.posix.fcntl; 594 auto flags = fcntl(fd, F_GETFD, 0); 595 if(flags == -1) 596 throw new ErrnoApiException("fcntl get", errno); 597 flags |= FD_CLOEXEC; 598 auto s = fcntl(fd, F_SETFD, flags); 599 if(s == -1) 600 throw new ErrnoApiException("fcntl set", errno); 601 } 602 603 604 /++ 605 A helper object for temporarily constructing a string appropriate for the Windows API from a D UTF-8 string. 606 607 608 It will use a small internal static buffer is possible, and allocate a new buffer if the string is too big. 609 610 History: 611 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 612 +/ 613 version(Windows) 614 struct WCharzBuffer { 615 private wchar[] buffer; 616 private wchar[128] staticBuffer = void; 617 618 /// Length of the string, excluding the zero terminator. 619 size_t length() { 620 return buffer.length; 621 } 622 623 // Returns the pointer to the internal buffer. You must assume its lifetime is less than that of the WCharzBuffer. It is zero-terminated. 624 wchar* ptr() { 625 return buffer.ptr; 626 } 627 628 /// Returns the slice of the internal buffer, excluding the zero terminator (though there is one present right off the end of the slice). You must assume its lifetime is less than that of the WCharzBuffer. 629 wchar[] slice() { 630 return buffer; 631 } 632 633 /// Copies it into a static array of wchars 634 void copyInto(R)(ref R r) { 635 static if(is(R == wchar[N], size_t N)) { 636 r[0 .. this.length] = slice[]; 637 r[this.length] = 0; 638 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 639 } 640 641 /++ 642 conversionFlags = [WindowsStringConversionFlags] 643 +/ 644 this(in char[] data, int conversionFlags = 0) { 645 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 646 auto sz = sizeOfConvertedWstring(data, conversionFlags); 647 if(sz > staticBuffer.length) 648 buffer = new wchar[](sz); 649 else 650 buffer = staticBuffer[]; 651 652 buffer = makeWindowsString(data, buffer, conversionFlags); 653 } 654 } 655 656 /++ 657 Alternative for toStringz 658 659 History: 660 Added March 18, 2023 (dub v11.0) 661 +/ 662 struct CharzBuffer { 663 private char[] buffer; 664 private char[128] staticBuffer = void; 665 666 /// Length of the string, excluding the zero terminator. 667 size_t length() { 668 assert(buffer.length > 0); 669 return buffer.length - 1; 670 } 671 672 // Returns the pointer to the internal buffer. You must assume its lifetime is less than that of the CharzBuffer. It is zero-terminated. 673 char* ptr() { 674 return buffer.ptr; 675 } 676 677 /// Returns the slice of the internal buffer, excluding the zero terminator (though there is one present right off the end of the slice). You must assume its lifetime is less than that of the CharzBuffer. 678 char[] slice() { 679 assert(buffer.length > 0); 680 return buffer[0 .. $-1]; 681 } 682 683 /// Copies it into a static array of chars 684 void copyInto(R)(ref R r) { 685 static if(is(R == char[N], size_t N)) { 686 r[0 .. this.length] = slice[]; 687 r[this.length] = 0; 688 } else static assert(0, "can only copy into char[n], not " ~ R.stringof); 689 } 690 691 @disable this(); 692 @disable this(this); 693 694 /++ 695 Copies `data` into the CharzBuffer, allocating a new one if needed, and zero-terminates it. 696 +/ 697 this(in char[] data) { 698 if(data.length + 1 > staticBuffer.length) 699 buffer = new char[](data.length + 1); 700 else 701 buffer = staticBuffer[]; 702 703 buffer[0 .. data.length] = data[]; 704 buffer[data.length] = 0; 705 } 706 } 707 708 /++ 709 Given the string `str`, converts it to a string compatible with the Windows API and puts the result in `buffer`, returning the slice of `buffer` actually used. `buffer` must be at least [sizeOfConvertedWstring] elements long. 710 711 History: 712 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 713 +/ 714 version(Windows) 715 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 716 if(str.length == 0) 717 return null; 718 719 int pos = 0; 720 dchar last; 721 foreach(dchar c; str) { 722 if(c <= 0xFFFF) { 723 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 724 buffer[pos++] = 13; 725 buffer[pos++] = cast(wchar) c; 726 } else if(c <= 0x10FFFF) { 727 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 728 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 729 } 730 731 last = c; 732 } 733 734 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 735 buffer[pos] = 0; 736 } 737 738 return buffer[0 .. pos]; 739 } 740 741 /++ 742 Converts the Windows API string `str` to a D UTF-8 string, storing it in `buffer`. Returns the slice of `buffer` actually used. 743 744 History: 745 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 746 +/ 747 version(Windows) 748 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 749 if(str.length == 0) 750 return null; 751 752 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 753 if(got == 0) { 754 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 755 throw new object.Exception("not enough buffer"); 756 else 757 throw new object.Exception("conversion"); // FIXME: GetLastError 758 } 759 return buffer[0 .. got]; 760 } 761 762 /++ 763 Converts the Windows API string `str` to a newly-allocated D UTF-8 string. 764 765 History: 766 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 767 +/ 768 version(Windows) 769 string makeUtf8StringFromWindowsString(in wchar[] str) { 770 char[] buffer; 771 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 772 buffer.length = got; 773 774 // it is unique because we just allocated it above! 775 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 776 } 777 778 /// ditto 779 version(Windows) 780 string makeUtf8StringFromWindowsString(wchar* str) { 781 char[] buffer; 782 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 783 buffer.length = got; 784 785 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 786 if(got == 0) { 787 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 788 throw new object.Exception("not enough buffer"); 789 else 790 throw new object.Exception("conversion"); // FIXME: GetLastError 791 } 792 return cast(string) buffer[0 .. got]; 793 } 794 795 // only used from minigui rn 796 package int findIndexOfZero(in wchar[] str) { 797 foreach(idx, wchar ch; str) 798 if(ch == 0) 799 return cast(int) idx; 800 return cast(int) str.length; 801 } 802 package int findIndexOfZero(in char[] str) { 803 foreach(idx, char ch; str) 804 if(ch == 0) 805 return cast(int) idx; 806 return cast(int) str.length; 807 } 808 809 /++ 810 Returns a minimum buffer length to hold the string `s` with the given conversions. It might be slightly larger than necessary, but is guaranteed to be big enough to hold it. 811 812 History: 813 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 814 +/ 815 version(Windows) 816 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 817 int size = 0; 818 819 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 820 // need to convert line endings, which means the length will get bigger. 821 822 // BTW I betcha this could be faster with some simd stuff. 823 char last; 824 foreach(char ch; s) { 825 if(ch == 10 && last != 13) 826 size++; // will add a 13 before it... 827 size++; 828 last = ch; 829 } 830 } else { 831 // no conversion necessary, just estimate based on length 832 /* 833 I don't think there's any string with a longer length 834 in code units when encoded in UTF-16 than it has in UTF-8. 835 This will probably over allocate, but that's OK. 836 */ 837 size = cast(int) s.length; 838 } 839 840 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 841 size++; 842 843 return size; 844 } 845 846 /++ 847 Used by [makeWindowsString] and [WCharzBuffer] 848 849 History: 850 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 851 +/ 852 version(Windows) 853 enum WindowsStringConversionFlags : int { 854 /++ 855 Append a zero terminator to the string. 856 +/ 857 zeroTerminate = 1, 858 /++ 859 Converts newlines from \n to \r\n. 860 +/ 861 convertNewLines = 2, 862 } 863 864 /++ 865 An int printing function that doesn't need to import Phobos. Can do some of the things std.conv.to and std.format.format do. 866 867 The buffer must be sized to hold the converted number. 32 chars is enough for most anything. 868 869 Returns: the slice of `buffer` containing the converted number. 870 +/ 871 char[] intToString(long value, char[] buffer, IntToStringArgs args = IntToStringArgs.init) { 872 const int radix = args.radix ? args.radix : 10; 873 const int digitsPad = args.padTo; 874 const int groupSize = args.groupSize; 875 876 int pos; 877 878 if(value < 0) { 879 buffer[pos++] = '-'; 880 value = -value; 881 } 882 883 int start = pos; 884 int digitCount; 885 886 do { 887 auto remainder = value % radix; 888 value = value / radix; 889 890 buffer[pos++] = cast(char) (remainder < 10 ? (remainder + '0') : (remainder - 10 + args.ten)); 891 digitCount++; 892 } while(value); 893 894 if(digitsPad > 0) { 895 while(digitCount < digitsPad) { 896 buffer[pos++] = args.padWith; 897 digitCount++; 898 } 899 } 900 901 assert(pos >= 1); 902 assert(pos - start > 0); 903 904 auto reverseSlice = buffer[start .. pos]; 905 for(int i = 0; i < reverseSlice.length / 2; i++) { 906 auto paired = cast(int) reverseSlice.length - i - 1; 907 char tmp = reverseSlice[i]; 908 reverseSlice[i] = reverseSlice[paired]; 909 reverseSlice[paired] = tmp; 910 } 911 912 return buffer[0 .. pos]; 913 } 914 915 /// ditto 916 struct IntToStringArgs { 917 private { 918 ubyte padTo; 919 char padWith; 920 ubyte radix; 921 char ten; 922 ubyte groupSize; 923 char separator; 924 } 925 926 IntToStringArgs withPadding(int padTo, char padWith = '0') { 927 IntToStringArgs args = this; 928 args.padTo = cast(ubyte) padTo; 929 args.padWith = padWith; 930 return args; 931 } 932 933 IntToStringArgs withRadix(int radix, char ten = 'a') { 934 IntToStringArgs args = this; 935 args.radix = cast(ubyte) radix; 936 args.ten = ten; 937 return args; 938 } 939 940 IntToStringArgs withGroupSeparator(int groupSize, char separator = '_') { 941 IntToStringArgs args = this; 942 args.groupSize = cast(ubyte) groupSize; 943 args.separator = separator; 944 return args; 945 } 946 } 947 948 unittest { 949 char[32] buffer; 950 assert(intToString(0, buffer[]) == "0"); 951 assert(intToString(-1, buffer[]) == "-1"); 952 assert(intToString(-132, buffer[]) == "-132"); 953 assert(intToString(-1932, buffer[]) == "-1932"); 954 assert(intToString(1, buffer[]) == "1"); 955 assert(intToString(132, buffer[]) == "132"); 956 assert(intToString(1932, buffer[]) == "1932"); 957 958 assert(intToString(0x1, buffer[], IntToStringArgs().withRadix(16)) == "1"); 959 assert(intToString(0x1b, buffer[], IntToStringArgs().withRadix(16)) == "1b"); 960 assert(intToString(0xef1, buffer[], IntToStringArgs().withRadix(16)) == "ef1"); 961 962 assert(intToString(0xef1, buffer[], IntToStringArgs().withRadix(16).withPadding(8)) == "00000ef1"); 963 assert(intToString(-0xef1, buffer[], IntToStringArgs().withRadix(16).withPadding(8)) == "-00000ef1"); 964 assert(intToString(-0xef1, buffer[], IntToStringArgs().withRadix(16, 'A').withPadding(8, ' ')) == "- EF1"); 965 } 966 967 /++ 968 History: 969 Moved from color.d to core.d in March 2023 (dub v11.0). 970 +/ 971 nothrow @safe @nogc pure 972 inout(char)[] stripInternal(return inout(char)[] s) { 973 bool isAllWhitespace = true; 974 foreach(i, char c; s) 975 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 976 s = s[i .. $]; 977 isAllWhitespace = false; 978 break; 979 } 980 981 if(isAllWhitespace) 982 return s[$..$]; 983 984 for(int a = cast(int)(s.length - 1); a > 0; a--) { 985 char c = s[a]; 986 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 987 s = s[0 .. a + 1]; 988 break; 989 } 990 } 991 992 return s; 993 } 994 995 /// ditto 996 nothrow @safe @nogc pure 997 inout(char)[] stripRightInternal(return inout(char)[] s) { 998 bool isAllWhitespace = true; 999 foreach_reverse(a, c; s) { 1000 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 1001 s = s[0 .. a + 1]; 1002 isAllWhitespace = false; 1003 break; 1004 } 1005 } 1006 if(isAllWhitespace) 1007 s = s[0..0]; 1008 1009 return s; 1010 1011 } 1012 1013 /++ 1014 Shortcut for converting some types to string without invoking Phobos (but it will as a last resort). 1015 1016 History: 1017 Moved from color.d to core.d in March 2023 (dub v11.0). 1018 +/ 1019 string toStringInternal(T)(T t) { 1020 char[32] buffer; 1021 static if(is(T : string)) 1022 return t; 1023 else static if(is(T : long)) 1024 return intToString(t, buffer[]).idup; 1025 else static if(is(T == enum)) { 1026 switch(t) { 1027 foreach(memberName; __traits(allMembers, T)) { 1028 case __traits(getMember, T, memberName): 1029 return memberName; 1030 } 1031 default: 1032 return "<unknown>"; 1033 } 1034 } else { 1035 import std.conv; 1036 return to!string(t); 1037 } 1038 } 1039 1040 /++ 1041 1042 +/ 1043 string flagsToString(Flags)(ulong value) { 1044 string r; 1045 1046 void add(string memberName) { 1047 if(r.length) 1048 r ~= " | "; 1049 r ~= memberName; 1050 } 1051 1052 string none = "<none>"; 1053 1054 foreach(memberName; __traits(allMembers, Flags)) { 1055 auto flag = cast(ulong) __traits(getMember, Flags, memberName); 1056 if(flag) { 1057 if((value & flag) == flag) 1058 add(memberName); 1059 } else { 1060 none = memberName; 1061 } 1062 } 1063 1064 if(r.length == 0) 1065 r = none; 1066 1067 return r; 1068 } 1069 1070 unittest { 1071 enum MyFlags { 1072 none = 0, 1073 a = 1, 1074 b = 2 1075 } 1076 1077 assert(flagsToString!MyFlags(3) == "a | b"); 1078 assert(flagsToString!MyFlags(0) == "none"); 1079 assert(flagsToString!MyFlags(2) == "b"); 1080 } 1081 1082 private auto toDelegate(T)(T t) { 1083 // static assert(is(T == function)); // lol idk how to do what i actually want here 1084 1085 static if(is(T Return == return)) 1086 static if(is(typeof(*T) Params == __parameters)) { 1087 static struct Wrapper { 1088 Return call(Params params) { 1089 return (cast(T) &this)(params); 1090 } 1091 } 1092 return &((cast(Wrapper*) t).call); 1093 } else static assert(0, "could not get params"); 1094 else static assert(0, "could not get return value"); 1095 } 1096 1097 unittest { 1098 int function(int) fn; 1099 fn = (a) { return a; }; 1100 1101 int delegate(int) dg = toDelegate(fn); 1102 1103 assert(dg.ptr is fn); // it stores the original function as the context pointer 1104 assert(dg.funcptr !is fn); // which is called through a lil trampoline 1105 assert(dg(5) == 5); // and forwards the args correctly 1106 } 1107 1108 /++ 1109 This populates a struct from a list of values (or other expressions, but it only looks at the values) based on types of the members, with one exception: `bool` members.. maybe. 1110 1111 It is intended for collecting a record of relevant UDAs off a symbol in a single call like this: 1112 1113 --- 1114 struct Name { 1115 string n; 1116 } 1117 1118 struct Validator { 1119 string regex; 1120 } 1121 1122 struct FormInfo { 1123 Name name; 1124 Validator validator; 1125 } 1126 1127 @Name("foo") @Validator(".*") 1128 void foo() {} 1129 1130 auto info = populateFromUdas!(FormInfo, __traits(getAttributes, foo)); 1131 assert(info.name == Name("foo")); 1132 assert(info.validator == Validator(".*")); 1133 --- 1134 1135 Note that instead of UDAs, you can also pass a variadic argument list and get the same result, but the function is `populateFromArgs` and you pass them as the runtime list to bypass "args cannot be evaluated at compile time" errors: 1136 1137 --- 1138 void foo(T...)(T t) { 1139 auto info = populateFromArgs!(FormInfo)(t); 1140 // assuming the call below 1141 assert(info.name == Name("foo")); 1142 assert(info.validator == Validator(".*")); 1143 } 1144 1145 foo(Name("foo"), Validator(".*")); 1146 --- 1147 1148 The benefit of this over constructing the struct directly is that the arguments can be reordered or missing. Its value is diminished with named arguments in the language. 1149 +/ 1150 template populateFromUdas(Struct, UDAs...) { 1151 enum Struct populateFromUdas = () { 1152 Struct ret; 1153 foreach(memberName; __traits(allMembers, Struct)) { 1154 alias memberType = typeof(__traits(getMember, Struct, memberName)); 1155 foreach(uda; UDAs) { 1156 static if(is(memberType == PresenceOf!a, a)) { 1157 static if(__traits(isSame, a, uda)) 1158 __traits(getMember, ret, memberName) = true; 1159 } 1160 else 1161 static if(is(typeof(uda) : memberType)) { 1162 __traits(getMember, ret, memberName) = uda; 1163 } 1164 } 1165 } 1166 1167 return ret; 1168 }(); 1169 } 1170 1171 /// ditto 1172 Struct populateFromArgs(Struct, Args...)(Args args) { 1173 Struct ret; 1174 foreach(memberName; __traits(allMembers, Struct)) { 1175 alias memberType = typeof(__traits(getMember, Struct, memberName)); 1176 foreach(arg; args) { 1177 static if(is(typeof(arg == memberType))) { 1178 __traits(getMember, ret, memberName) = arg; 1179 } 1180 } 1181 } 1182 1183 return ret; 1184 } 1185 1186 /// ditto 1187 struct PresenceOf(alias a) { 1188 bool there; 1189 alias there this; 1190 } 1191 1192 /// 1193 unittest { 1194 enum a; 1195 enum b; 1196 struct Name { string name; } 1197 struct Info { 1198 Name n; 1199 PresenceOf!a athere; 1200 PresenceOf!b bthere; 1201 int c; 1202 } 1203 1204 void test() @a @Name("test") {} 1205 1206 auto info = populateFromUdas!(Info, __traits(getAttributes, test)); 1207 assert(info.n == Name("test")); // but present ones are in there 1208 assert(info.athere == true); // non-values can be tested with PresenceOf!it, which works like a bool 1209 assert(info.bthere == false); 1210 assert(info.c == 0); // absent thing will keep the default value 1211 } 1212 1213 /++ 1214 Declares a delegate property with several setters to allow for handlers that don't care about the arguments. 1215 1216 Throughout the arsd library, you will often see types of these to indicate that you can set listeners with or without arguments. If you care about the details of the callback event, you can set a delegate that declares them. And if you don't, you can set one that doesn't even declare them and it will be ignored. 1217 +/ 1218 struct FlexibleDelegate(DelegateType) { 1219 // please note that Parameters and ReturnType are public now! 1220 static if(is(DelegateType FunctionType == delegate)) 1221 static if(is(FunctionType Parameters == __parameters)) 1222 static if(is(DelegateType ReturnType == return)) { 1223 1224 /++ 1225 Calls the currently set delegate. 1226 1227 Diagnostics: 1228 If the callback delegate has not been set, this may cause a null pointer dereference. 1229 +/ 1230 ReturnType opCall(Parameters args) { 1231 return dg(args); 1232 } 1233 1234 /++ 1235 Use `if(thing)` to check if the delegate is null or not. 1236 +/ 1237 bool opCast(T : bool)() { 1238 return dg !is null; 1239 } 1240 1241 /++ 1242 These opAssign overloads are what puts the flexibility in the flexible delegate. 1243 1244 Bugs: 1245 The other overloads do not keep attributes like `nothrow` on the `dg` parameter, making them unusable if `DelegateType` requires them. I consider the attributes more trouble than they're worth anyway, and the language's poor support for composing them doesn't help any. I have no need for them and thus no plans to add them in the overloads at this time. 1246 +/ 1247 void opAssign(DelegateType dg) { 1248 this.dg = dg; 1249 } 1250 1251 /// ditto 1252 void opAssign(ReturnType delegate() dg) { 1253 this.dg = (Parameters ignored) => dg(); 1254 } 1255 1256 /// ditto 1257 void opAssign(ReturnType function(Parameters params) dg) { 1258 this.dg = (Parameters params) => dg(params); 1259 } 1260 1261 /// ditto 1262 void opAssign(ReturnType function() dg) { 1263 this.dg = (Parameters ignored) => dg(); 1264 } 1265 1266 /// ditto 1267 void opAssign(typeof(null) explicitNull) { 1268 this.dg = null; 1269 } 1270 1271 private DelegateType dg; 1272 } 1273 else static assert(0, DelegateType.stringof ~ " failed return value check"); 1274 else static assert(0, DelegateType.stringof ~ " failed parameters check"); 1275 else static assert(0, DelegateType.stringof ~ " failed delegate check"); 1276 } 1277 1278 /++ 1279 1280 +/ 1281 unittest { 1282 // you don't have to put the arguments in a struct, but i recommend 1283 // you do as it is more future proof - you can add more info to the 1284 // struct without breaking user code that consumes it. 1285 struct MyEventArguments { 1286 1287 } 1288 1289 // then you declare it just adding FlexibleDelegate!() around the 1290 // plain delegate type you'd normally use 1291 FlexibleDelegate!(void delegate(MyEventArguments args)) callback; 1292 1293 // until you set it, it will be null and thus be false in any boolean check 1294 assert(!callback); 1295 1296 // can set it to the properly typed thing 1297 callback = delegate(MyEventArguments args) {}; 1298 1299 // and now it is no longer null 1300 assert(callback); 1301 1302 // or if you don't care about the args, you can leave them off 1303 callback = () {}; 1304 1305 // and it works if the compiler types you as a function instead of delegate too 1306 // (which happens automatically if you don't access any local state or if you 1307 // explicitly define it as a function) 1308 1309 callback = function(MyEventArguments args) { }; 1310 1311 // can set it back to null explicitly if you ever wanted 1312 callback = null; 1313 1314 // the reflection info used internally also happens to be exposed publicly 1315 // which can actually sometimes be nice so if the language changes, i'll change 1316 // the code to keep this working. 1317 static assert(is(callback.ReturnType == void)); 1318 1319 // which can be convenient if the params is an annoying type since you can 1320 // consistently use something like this too 1321 callback = (callback.Parameters params) {}; 1322 1323 // check for null and call it pretty normally 1324 if(callback) 1325 callback(MyEventArguments()); 1326 } 1327 1328 /+ 1329 ====================== 1330 ERROR HANDLING HELPERS 1331 ====================== 1332 +/ 1333 1334 /+ + 1335 arsd code shouldn't be using Exception. Really, I don't think any code should be - instead, construct an appropriate object with structured information. 1336 1337 If you want to catch someone else's Exception, use `catch(object.Exception e)`. 1338 +/ 1339 //package deprecated struct Exception {} 1340 1341 1342 /++ 1343 Base class representing my exceptions. You should almost never work with this directly, but you might catch it as a generic thing. Catch it before generic `object.Exception` or `object.Throwable` in any catch chains. 1344 1345 1346 $(H3 General guidelines for exceptions) 1347 1348 The purpose of an exception is to cancel a task that has proven to be impossible and give the programmer enough information to use at a higher level to decide what to do about it. 1349 1350 Cancelling a task is accomplished with the `throw` keyword. The transmission of information to a higher level is done by the language runtime. The decision point is marked by the `catch` keyword. The part missing - the job of the `Exception` class you construct and throw - is to gather the information that will be useful at a later decision point. 1351 1352 It is thus important that you gather as much useful information as possible and keep it in a way that the code catching the exception can still interpret it when constructing an exception. Other concerns are secondary to this to this primary goal. 1353 1354 With this in mind, here's some guidelines for exception handling in arsd code. 1355 1356 $(H4 Allocations and lifetimes) 1357 1358 Don't get clever with exception allocations. You don't know what the catcher is going to do with an exception and you don't want the error handling scheme to introduce its own tricky bugs. Remember, an exception object's first job is to deliver useful information up the call chain in a way this code can use it. You don't know what this code is or what it is going to do. 1359 1360 Keep your memory management schemes simple and let the garbage collector do its job. 1361 1362 $(LIST 1363 * All thrown exceptions should be allocated with the `new` keyword. 1364 1365 * Members inside the exception should be value types or have infinite lifetime (that is, be GC managed). 1366 1367 * While this document is concerned with throwing, you might want to add additional information to an in-flight exception, and this is done by catching, so you need to know how that works too, and there is a global compiler switch that can change things, so even inside arsd we can't completely avoid its implications. 1368 1369 DIP1008's presence complicates things a bit on the catch side - if you catch an exception and return it from a function, remember to `ex.refcount = ex.refcount + 1;` so you don't introduce more use-after-free woes for those unfortunate souls. 1370 ) 1371 1372 $(H4 Error strings) 1373 1374 Strings can deliver useful information to people reading the message, but are often suboptimal for delivering useful information to other chunks of code. Remember, an exception's first job is to be caught by another block of code. Printing to users is a last resort; even if you want a user-readable error message, an exception is not the ideal way to deliver one since it is constructed in the guts of a failed task, without the higher level context of what the user was actually trying to do. User error messages ought to be made from information in the exception, combined with higher level knowledge. This is best done in a `catch` block, not a `throw` statement. 1375 1376 As such, I recommend that you: 1377 1378 $(LIST 1379 * Don't concatenate error strings at the throw site. Instead, pass the data you would have used to build the string as actual data to the constructor. This lets catchers see the original data without having to try to extract it from a string. For unique data, you will likely need a unique exception type. More on this in the next section. 1380 1381 * Don't construct error strings in a constructor either, for the same reason. Pass the useful data up the call chain, as exception members, to the maximum extent possible. Exception: if you are passed some data with a temporary lifetime that is important enough to pass up the chain. You may `.idup` or `to!string` to preserve as much data as you can before it is lost, but still store it in a separate member of the Exception subclass object. 1382 1383 * $(I Do) construct strings out of public members in [getAdditionalPrintableInformation]. When this is called, the user has requested as much relevant information as reasonable in string format. Still, avoid concatenation - it lets you pass as many key/value pairs as you like to the caller. They can concatenate as needed. However, note the words "public members" - everything you do in `getAdditionalPrintableInformation` ought to also be possible for code that caught your exception via your public methods and properties. 1384 ) 1385 1386 $(H4 Subclasses) 1387 1388 Any exception with unique data types should be a unique class. Whenever practical, this should be one you write and document at the top-level of a module. But I know we get lazy - me too - and this is why in standard D we'd often fall back to `throw new Exception("some string " ~ some info)`. To help resist these urges, I offer some helper functions to use instead that better achieve the key goal of exceptions - passing structured data up a call chain - while still being convenient to write. 1389 1390 See: [ArsdException], [Win32Enforce] 1391 1392 +/ 1393 class ArsdExceptionBase : object.Exception { 1394 /++ 1395 Don't call this except from other exceptions; this is essentially an abstract class. 1396 1397 Params: 1398 operation = the specific operation that failed, throwing the exception 1399 +/ 1400 package this(string operation, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1401 super(operation, file, line, next); 1402 } 1403 1404 /++ 1405 The toString method will print out several components: 1406 1407 $(LIST 1408 * The file, line, static message, and object class name from the constructor. You can access these independently with the members `file`, `line`, `msg`, and [printableExceptionName]. 1409 * The generic category codes stored with this exception 1410 * Additional members stored with the exception child classes (e.g. platform error codes, associated function arguments) 1411 * The stack trace associated with the exception. You can access these lines independently with `foreach` over the `info` member. 1412 ) 1413 1414 This is meant to be read by the developer, not end users. You should wrap your user-relevant tasks in a try/catch block and construct more appropriate error messages from context available there, using the individual properties of the exception to add richness. 1415 +/ 1416 final override void toString(scope void delegate(in char[]) sink) const { 1417 // class name and info from constructor 1418 sink(printableExceptionName); 1419 sink("@"); 1420 sink(file); 1421 sink("("); 1422 char[16] buffer; 1423 sink(intToString(line, buffer[])); 1424 sink("): "); 1425 sink(message); 1426 1427 getAdditionalPrintableInformation((string name, in char[] value) { 1428 sink("\n"); 1429 sink(name); 1430 sink(": "); 1431 sink(value); 1432 }); 1433 1434 // full stack trace 1435 sink("\n----------------\n"); 1436 foreach(str; info) { 1437 sink(str); 1438 sink("\n"); 1439 } 1440 } 1441 /// ditto 1442 final override string toString() { 1443 string s; 1444 toString((in char[] chunk) { s ~= chunk; }); 1445 return s; 1446 } 1447 1448 /++ 1449 Users might like to see additional information with the exception. API consumers should pull this out of properties on your child class, but the parent class might not be able to deal with the arbitrary types at runtime the children can introduce, so bringing them all down to strings simplifies that. 1450 1451 Overrides should always call `super.getAdditionalPrintableInformation(sink);` before adding additional information by calling the sink with other arguments afterward. 1452 1453 You should spare no expense in preparing this information - translate error codes, build rich strings, whatever it takes - to make the information here useful to the reader. 1454 +/ 1455 void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1456 1457 } 1458 1459 /++ 1460 This is the name of the exception class, suitable for printing. This should be static data (e.g. a string literal). Override it in subclasses. 1461 +/ 1462 string printableExceptionName() const { 1463 return typeid(this).name; 1464 } 1465 1466 /// deliberately hiding `Throwable.msg`. Use [message] and [toString] instead. 1467 @disable final void msg() {} 1468 1469 override const(char)[] message() const { 1470 return super.msg; 1471 } 1472 } 1473 1474 /++ 1475 1476 +/ 1477 class InvalidArgumentsException : ArsdExceptionBase { 1478 static struct InvalidArgument { 1479 string name; 1480 string description; 1481 LimitedVariant givenValue; 1482 } 1483 1484 InvalidArgument[] invalidArguments; 1485 1486 this(InvalidArgument[] invalidArguments, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1487 this.invalidArguments = invalidArguments; 1488 super(functionName, file, line, next); 1489 } 1490 1491 this(string argumentName, string argumentDescription, LimitedVariant givenArgumentValue = LimitedVariant.init, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1492 this([ 1493 InvalidArgument(argumentName, argumentDescription, givenArgumentValue) 1494 ], functionName, file, line, next); 1495 } 1496 1497 this(string argumentName, string argumentDescription, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1498 this(argumentName, argumentDescription, LimitedVariant.init, functionName, file, line, next); 1499 } 1500 1501 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1502 // FIXME: print the details better 1503 foreach(arg; invalidArguments) 1504 sink("invalidArguments[]", arg.name ~ " " ~ arg.description); 1505 } 1506 } 1507 1508 /++ 1509 Base class for when you've requested a feature that is not available. It may not be available because it is possible, but not yet implemented, or it might be because it is impossible on your operating system. 1510 +/ 1511 class FeatureUnavailableException : ArsdExceptionBase { 1512 this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1513 super(featureName, file, line, next); 1514 } 1515 } 1516 1517 /++ 1518 This means the feature could be done, but I haven't gotten around to implementing it yet. If you email me, I might be able to add it somewhat quickly and get back to you. 1519 +/ 1520 class NotYetImplementedException : FeatureUnavailableException { 1521 this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1522 super(featureName, file, line, next); 1523 } 1524 1525 } 1526 1527 /++ 1528 This means the feature is not supported by your current operating system. You might be able to get it in an update, but you might just have to find an alternate way of doing things. 1529 +/ 1530 class NotSupportedException : FeatureUnavailableException { 1531 this(string featureName, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1532 super(featureName, file, line, next); 1533 } 1534 } 1535 1536 /++ 1537 This is a generic exception with attached arguments. It is used when I had to throw something but didn't want to write a new class. 1538 1539 You can catch an ArsdException to get its passed arguments out. 1540 1541 You can pass either a base class or a string as `Type`. 1542 1543 See the examples for how to use it. 1544 +/ 1545 template ArsdException(alias Type, DataTuple...) { 1546 static if(DataTuple.length) 1547 alias Parent = ArsdException!(Type, DataTuple[0 .. $-1]); 1548 else 1549 alias Parent = ArsdExceptionBase; 1550 1551 class ArsdException : Parent { 1552 DataTuple data; 1553 1554 this(DataTuple data, string file = __FILE__, size_t line = __LINE__) { 1555 this.data = data; 1556 static if(is(Parent == ArsdExceptionBase)) 1557 super(null, file, line); 1558 else 1559 super(data[0 .. $-1], file, line); 1560 } 1561 1562 static opCall(R...)(R r, string file = __FILE__, size_t line = __LINE__) { 1563 return new ArsdException!(Type, DataTuple, R)(r, file, line); 1564 } 1565 1566 override string printableExceptionName() const { 1567 static if(DataTuple.length) 1568 enum str = "ArsdException!(" ~ Type.stringof ~ ", " ~ DataTuple.stringof[1 .. $-1] ~ ")"; 1569 else 1570 enum str = "ArsdException!" ~ Type.stringof; 1571 return str; 1572 } 1573 1574 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1575 ArsdExceptionBase.getAdditionalPrintableInformation(sink); 1576 1577 foreach(idx, datum; data) { 1578 enum int lol = cast(int) idx; 1579 enum key = "[" ~ lol.stringof ~ "] " ~ DataTuple[idx].stringof; 1580 sink(key, toStringInternal(datum)); 1581 } 1582 } 1583 } 1584 } 1585 1586 /// This example shows how you can throw and catch the ad-hoc exception types. 1587 unittest { 1588 // you can throw and catch by matching the string and argument types 1589 try { 1590 // throw it with parenthesis after the template args (it uses opCall to construct) 1591 throw ArsdException!"Test"(); 1592 // you could also `throw new ArsdException!"test";`, but that gets harder with args 1593 // as we'll see in the following example 1594 assert(0); // remove from docs 1595 } catch(ArsdException!"Test" e) { // catch it without them 1596 // this has no useful information except for the type 1597 // but you can catch it like this and it is still more than generic Exception 1598 } 1599 1600 // an exception's job is to deliver useful information up the chain 1601 // and you can do that easily by passing arguments: 1602 1603 try { 1604 throw ArsdException!"Test"(4, "four"); 1605 // you could also `throw new ArsdException!("Test", int, string)(4, "four")` 1606 // but now you start to see how the opCall convenience constructor simplifies things 1607 assert(0); // remove from docs 1608 } catch(ArsdException!("Test", int, string) e) { // catch it and use info by specifying types 1609 assert(e.data[0] == 4); // and extract arguments like this 1610 assert(e.data[1] == "four"); 1611 } 1612 1613 // a throw site can add additional information without breaking code that catches just some 1614 // generally speaking, each additional argument creates a new subclass on top of the previous args 1615 // so you can cast 1616 1617 try { 1618 throw ArsdException!"Test"(4, "four", 9); 1619 assert(0); // remove from docs 1620 } catch(ArsdException!("Test", int, string) e) { // this catch still works 1621 assert(e.data[0] == 4); 1622 assert(e.data[1] == "four"); 1623 // but if you were to print it, all the members would be there 1624 // import std.stdio; writeln(e); // would show something like: 1625 /+ 1626 ArsdException!("Test", int, string, int)@file.d(line): 1627 [0] int: 4 1628 [1] string: four 1629 [2] int: 9 1630 +/ 1631 // indicating that there's additional information available if you wanted to process it 1632 1633 // and meanwhile: 1634 ArsdException!("Test", int) e2 = e; // this implicit cast works thanks to the parent-child relationship 1635 ArsdException!"Test" e3 = e; // this works too, the base type/string still matches 1636 1637 // so catching those types would work too 1638 } 1639 } 1640 1641 /++ 1642 A tagged union that holds an error code from system apis, meaning one from Windows GetLastError() or C's errno. 1643 1644 You construct it with `SystemErrorCode(thing)` and the overloaded constructor tags and stores it. 1645 +/ 1646 struct SystemErrorCode { 1647 /// 1648 enum Type { 1649 errno, /// 1650 win32 /// 1651 } 1652 1653 const Type type; /// 1654 const int code; /// You should technically cast it back to DWORD if it is a win32 code 1655 1656 /++ 1657 C/unix error are typed as signed ints... 1658 Windows' errors are typed DWORD, aka unsigned... 1659 1660 so just passing them straight up will pick the right overload here to set the tag. 1661 +/ 1662 this(int errno) { 1663 this.type = Type.errno; 1664 this.code = errno; 1665 } 1666 1667 /// ditto 1668 this(uint win32) { 1669 this.type = Type.win32; 1670 this.code = win32; 1671 } 1672 1673 /++ 1674 Returns if the code indicated success. 1675 1676 Please note that many calls do not actually set a code to success, but rather just don't touch it. Thus this may only be true on `init`. 1677 +/ 1678 bool wasSuccessful() const { 1679 final switch(type) { 1680 case Type.errno: 1681 return this.code == 0; 1682 case Type.win32: 1683 return this.code == 0; 1684 } 1685 } 1686 1687 /++ 1688 Constructs a string containing both the code and the explanation string. 1689 +/ 1690 string toString() const { 1691 return "[" ~ codeAsString ~ "] " ~ errorString; 1692 } 1693 1694 /++ 1695 The numeric code itself as a string. 1696 1697 See [errorString] for a text explanation of the code. 1698 +/ 1699 string codeAsString() const { 1700 char[16] buffer; 1701 final switch(type) { 1702 case Type.errno: 1703 return intToString(code, buffer[]).idup; 1704 case Type.win32: 1705 buffer[0 .. 2] = "0x"; 1706 return buffer[0 .. 2 + intToString(cast(uint) code, buffer[2 .. $], IntToStringArgs().withRadix(16).withPadding(8)).length].idup; 1707 } 1708 } 1709 1710 /++ 1711 A text explanation of the code. See [codeAsString] for a string representation of the numeric representation. 1712 +/ 1713 string errorString() const { 1714 final switch(type) { 1715 case Type.errno: 1716 import core.stdc.string; 1717 auto strptr = strerror(code); 1718 auto orig = strptr; 1719 int len; 1720 while(*strptr++) { 1721 len++; 1722 } 1723 1724 return orig[0 .. len].idup; 1725 case Type.win32: 1726 version(Windows) { 1727 wchar[256] buffer; 1728 auto size = FormatMessageW( 1729 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 1730 null, 1731 code, 1732 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 1733 buffer.ptr, 1734 buffer.length, 1735 null 1736 ); 1737 1738 return makeUtf8StringFromWindowsString(buffer[0 .. size]).stripInternal; 1739 } else { 1740 return null; 1741 } 1742 } 1743 } 1744 } 1745 1746 /++ 1747 1748 +/ 1749 struct SavedArgument { 1750 string name; 1751 LimitedVariant value; 1752 } 1753 1754 /++ 1755 1756 +/ 1757 class SystemApiException : ArsdExceptionBase { 1758 this(string msg, int originalErrorNo, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1759 this(msg, SystemErrorCode(originalErrorNo), args, file, line, next); 1760 } 1761 1762 version(Windows) 1763 this(string msg, DWORD windowsError, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1764 this(msg, SystemErrorCode(windowsError), args, file, line, next); 1765 } 1766 1767 this(string msg, SystemErrorCode code, SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1768 this.errorCode = code; 1769 1770 // discard stuff that won't fit 1771 if(args.length > this.args.length) 1772 args = args[0 .. this.args.length]; 1773 1774 this.args[0 .. args.length] = args[]; 1775 1776 super(msg, file, line, next); 1777 } 1778 1779 /++ 1780 1781 +/ 1782 const SystemErrorCode errorCode; 1783 1784 /++ 1785 1786 +/ 1787 const SavedArgument[8] args; 1788 1789 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 1790 super.getAdditionalPrintableInformation(sink); 1791 sink("Error code", errorCode.toString()); 1792 1793 foreach(arg; args) 1794 if(arg.name !is null) 1795 sink(arg.name, arg.value.toString()); 1796 } 1797 1798 } 1799 1800 /++ 1801 The low level use of this would look like `throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError())` but it is meant to be used from higher level things like [Win32Enforce]. 1802 1803 History: 1804 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1805 +/ 1806 alias WindowsApiException = SystemApiException; 1807 1808 /++ 1809 History: 1810 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1811 +/ 1812 alias ErrnoApiException = SystemApiException; 1813 1814 /++ 1815 Calls the C API function `fn`. If it returns an error value, it throws an [ErrnoApiException] (or subclass) after getting `errno`. 1816 +/ 1817 template ErrnoEnforce(alias fn, alias errorValue = void) { 1818 static if(is(typeof(fn) Return == return)) 1819 static if(is(typeof(fn) Params == __parameters)) { 1820 static if(is(errorValue == void)) { 1821 static if(is(typeof(null) : Return)) 1822 enum errorValueToUse = null; 1823 else static if(is(Return : long)) 1824 enum errorValueToUse = -1; 1825 else 1826 static assert(0, "Please pass the error value"); 1827 } else { 1828 enum errorValueToUse = errorValue; 1829 } 1830 1831 Return ErrnoEnforce(Params params, ArgSentinel sentinel = ArgSentinel.init, string file = __FILE__, size_t line = __LINE__) { 1832 import core.stdc.errno; 1833 1834 Return value = fn(params); 1835 1836 if(value == errorValueToUse) { 1837 SavedArgument[] args; // FIXME 1838 /+ 1839 static foreach(idx; 0 .. Params.length) 1840 args ~= SavedArgument( 1841 __traits(identifier, Params[idx .. idx + 1]), 1842 params[idx] 1843 ); 1844 +/ 1845 throw new ErrnoApiException(__traits(identifier, fn), errno, args, file, line); 1846 } 1847 1848 return value; 1849 } 1850 } 1851 } 1852 1853 version(Windows) { 1854 /++ 1855 Calls the Windows API function `fn`. If it returns an error value, it throws a [WindowsApiException] (or subclass) after calling `GetLastError()`. 1856 +/ 1857 template Win32Enforce(alias fn, alias errorValue = void) { 1858 static if(is(typeof(fn) Return == return)) 1859 static if(is(typeof(fn) Params == __parameters)) { 1860 static if(is(errorValue == void)) { 1861 static if(is(Return == BOOL)) 1862 enum errorValueToUse = false; 1863 else static if(is(Return : HANDLE)) 1864 enum errorValueToUse = NULL; 1865 else static if(is(Return == DWORD)) 1866 enum errorValueToUse = cast(DWORD) 0xffffffff; 1867 else 1868 static assert(0, "Please pass the error value"); 1869 } else { 1870 enum errorValueToUse = errorValue; 1871 } 1872 1873 Return Win32Enforce(Params params, ArgSentinel sentinel = ArgSentinel.init, string file = __FILE__, size_t line = __LINE__) { 1874 Return value = fn(params); 1875 1876 if(value == errorValueToUse) { 1877 auto error = GetLastError(); 1878 SavedArgument[] args; // FIXME 1879 throw new WindowsApiException(__traits(identifier, fn), error, args, file, line); 1880 } 1881 1882 return value; 1883 } 1884 } 1885 } 1886 1887 } 1888 1889 /+ 1890 =============== 1891 EVENT LOOP CORE 1892 =============== 1893 +/ 1894 1895 /+ 1896 UI threads 1897 need to get window messages in addition to all the other jobs 1898 I/O Worker threads 1899 need to get commands for read/writes, run them, and send the reply back. not necessary on Windows 1900 if interrupted, check cancel flags. 1901 CPU Worker threads 1902 gets functions, runs them, send reply back. should send a cancel flag to periodically check 1903 Task worker threads 1904 runs fibers and multiplexes them 1905 1906 1907 General procedure: 1908 issue the read/write command 1909 if it would block on linux, epoll associate it. otherwise do the callback immediately 1910 1911 callbacks have default affinity to the current thread, meaning their callbacks always run here 1912 accepts can usually be dispatched to any available thread tho 1913 1914 // In other words, a single thread can be associated with, at most, one I/O completion port. 1915 1916 Realistically, IOCP only used if there is no thread affinity. If there is, just do overlapped w/ sleepex. 1917 1918 1919 case study: http server 1920 1921 1) main thread starts the server. it does an accept loop with no thread affinity. the main thread does NOT check the global queue (the iocp/global epoll) 1922 2) connections come in and are assigned to first available thread via the iocp/global epoll 1923 3) these run local event loops until the connection task is finished 1924 1925 EVENT LOOP TYPES: 1926 1) main ui thread - MsgWaitForMultipleObjectsEx / epoll on the local ui. it does NOT check the any worker thread thing! 1927 The main ui thread should never terminate until the program is ready to close. 1928 You can have additional ui threads in theory but im not really gonna support that in full; most things will assume there is just the one. simpledisplay's gui thread is the primary if it exists. (and sdpy will prolly continue to be threaded the way it is now) 1929 1930 The biggest complication is the TerminalDirectToEmulator, where the primary ui thread is NOT the thread that runs `main` 1931 2) worker thread GetQueuedCompletionStatusEx / epoll on the local thread fd and the global epoll fd 1932 3) local event loop - check local things only. SleepEx / epoll on local thread fd. This more of a compatibility hack for `waitForCompletion` outside a fiber. 1933 1934 i'll use: 1935 * QueueUserAPC to send interruptions to a worker thread 1936 * PostQueuedCompletionStatus is to send interruptions to any available thread. 1937 * PostMessage to a window 1938 * ??? to a fiber task 1939 1940 I also need a way to de-duplicate events in the queue so if you try to push the same thing it won't trigger multiple times.... I might want to keep a duplicate of the thing... really, what I'd do is post the "event wake up" message and keep the queue in my own thing. (WM_PAINT auto-coalesces) 1941 1942 Destructors need to be able to post messages back to a specific task to queue thread-affinity cleanup. This must be GC safe. 1943 1944 A task might want to wait on certain events. If the task is a fiber, it yields and gets called upon the event. If the task is a thread, it really has to call the event loop... which can be a loop of loops we want to avoid. `waitForCompletion` is more often gonna be used just to run the loop at top level tho... it might not even check for the global info availability so it'd run the local thing only. 1945 1946 APCs should not themselves enter an alterable wait cuz it can stack overflow. So generally speaking, they should avoid calling fibers or other event loops. 1947 +/ 1948 1949 /++ 1950 You can also pass a handle to a specific thread, if you have one. 1951 +/ 1952 enum ThreadToRunIn { 1953 /++ 1954 The callback should be only run by the same thread that set it. 1955 +/ 1956 CurrentThread, 1957 /++ 1958 The UI thread is a special one - it is the supervisor of the workers and the controller of gui and console handles. It is the first thread to call [arsd_core_init] actively running an event loop unless there is a thread that has actively asserted the ui supervisor role. FIXME is this true after i implemen it? 1959 1960 A ui thread should be always quickly responsive to new events. 1961 1962 There should only be one main ui thread, in which simpledisplay and minigui can be used. 1963 1964 Other threads can run like ui threads, but are considered temporary and only concerned with their own needs (it is the default style of loop 1965 for an undeclared thread but will not receive messages from other threads unless there is no other option) 1966 1967 1968 Ad-Hoc thread - something running an event loop that isn't another thing 1969 Controller thread - running an explicit event loop instance set as not a task runner or blocking worker 1970 UI thread - simpledisplay's event loop, which it will require remain live for the duration of the program (running two .eventLoops without a parent EventLoop instance will become illegal, throwing at runtime if it happens telling people to change their code) 1971 1972 Windows HANDLES will always be listened on the thread itself that is requesting, UNLESS it is a worker/helper thread, in which case it goes to a coordinator thread. since it prolly can't rely on the parent per se this will have to be one created by arsd core init, UNLESS the parent is inside an explicit EventLoop structure. 1973 1974 All use the MsgWaitForMultipleObjectsEx pattern 1975 1976 1977 +/ 1978 UiThread, 1979 /++ 1980 The callback can be called from any available worker thread. It will be added to a global queue and the first thread to see it will run it. 1981 1982 These will not run on the UI thread unless there is no other option on the platform (and all platforms this lib supports have other options). 1983 1984 These are expected to run cooperatively multitasked things; functions that frequently yield as they wait on other tasks. Think a fiber. 1985 1986 A task runner should be generally responsive to new events. 1987 +/ 1988 AnyAvailableTaskRunnerThread, 1989 /++ 1990 These are expected to run longer blocking, but independent operations. Think an individual function with no context. 1991 1992 A blocking worker can wait hundreds of milliseconds between checking for new events. 1993 +/ 1994 AnyAvailableBlockingWorkerThread, 1995 /++ 1996 The callback will be duplicated across all threads known to the arsd.core event loop. 1997 1998 It adds it to an immutable queue that each thread will go through... might just replace with an exit() function. 1999 2000 2001 so to cancel all associated tasks for like a web server, it could just have the tasks atomicAdd to a counter and subtract when they are finished. Then you have a single semaphore you signal the number of times you have an active thing and wait for them to acknowledge it. 2002 2003 threads should report when they start running the loop and they really should report when they terminate but that isn't reliable 2004 2005 2006 hmmm what if: all user-created threads (the public api) count as ui threads. only ones created in here are task runners or helpers. ui threads can wait on a global event to exit. 2007 2008 there's still prolly be one "the" ui thread, which does the handle listening on windows and is the one sdpy wants. 2009 +/ 2010 BroadcastToAllThreads, 2011 } 2012 2013 /++ 2014 Initializes the arsd core event loop and creates its worker threads. You don't actually have to call this, since the first use of an arsd.core function that requires it will call it implicitly, but calling it yourself gives you a chance to control the configuration more explicitly if you want to. 2015 +/ 2016 void arsd_core_init(int numberOfWorkers = 0) { 2017 2018 } 2019 2020 version(Windows) 2021 class WindowsHandleReader_ex { 2022 // Windows handles are always dispatched to the main ui thread, which can then send a command back to a worker thread to run the callback if needed 2023 this(HANDLE handle) {} 2024 } 2025 2026 version(Posix) 2027 class PosixFdReader_ex { 2028 // posix readers can just register with whatever instance we want to handle the callback 2029 } 2030 2031 /++ 2032 2033 +/ 2034 interface ICoreEventLoop { 2035 /++ 2036 Runs the event loop for this thread until the `until` delegate returns `true`. 2037 +/ 2038 final void run(scope bool delegate() until) { 2039 while(!exitApplicationRequested && !until()) { 2040 runOnce(); 2041 } 2042 } 2043 2044 private __gshared bool exitApplicationRequested; 2045 2046 final static void exitApplication() { 2047 exitApplicationRequested = true; 2048 // FIXME: wake up all the threads 2049 } 2050 2051 /++ 2052 Returns details from a call to [runOnce]. Use the named methods here for details, or it can be used in a `while` loop directly thanks to its `opCast` automatic conversion to `bool`. 2053 2054 History: 2055 Added December 28, 2023 2056 +/ 2057 static struct RunOnceResult { 2058 enum Possibilities { 2059 CarryOn, 2060 LocalExit, 2061 GlobalExit, 2062 Interrupted 2063 2064 } 2065 Possibilities result; 2066 2067 /++ 2068 Returns `true` if the event loop should generally continue. 2069 2070 Might be false if the local loop was exited or if the application is supposed to exit. If this is `false`, check [applicationExitRequested] to determine if you should move on to other work or start your final cleanup process. 2071 +/ 2072 bool shouldContinue() const { 2073 return result == Possibilities.CarryOn; 2074 } 2075 2076 /++ 2077 Returns `true` if [ICoreEventLoop.exitApplication] was called during this event, or if the user or operating system has requested the application exit. 2078 2079 Details might be available through other means. 2080 +/ 2081 bool applicationExitRequested() const { 2082 return result == Possibilities.GlobalExit; 2083 } 2084 2085 /++ 2086 Returns [shouldContinue] when used in a context for an implicit bool (e.g. `if` statements). 2087 +/ 2088 bool opCast(T : bool)() const { 2089 reutrn shouldContinue(); 2090 } 2091 } 2092 2093 /++ 2094 Runs a single iteration of the event loop for this thread. It will return when the first thing happens, but that thing might be totally uninteresting to anyone, or it might trigger significant work you'll wait on. 2095 2096 Note that running this externally instead of `run` gives only the $(I illusion) of control. You're actually better off setting a recurring timer if you need things to run on a clock tick, or a single-shot timer for a one time event. They're more likely to be called on schedule inside this function than outside it. 2097 2098 Parameters: 2099 timeout = a timeout value for an idle loop. There is no guarantee you won't return earlier or later than this; the function might run longer than the timeout if it has work to do. Pass `Duration.max` (the default) for an infinite duration timeout (but remember, once it finds work to do, including a false-positive wakeup or interruption by the operating system, it will return early anyway). 2100 2101 History: 2102 Prior to December 28, 2023, it returned `void` and took no arguments. This change is breaking, but since the entire module is documented as unstable, it was permitted to happen as that document provided prior notice. 2103 +/ 2104 RunOnceResult runOnce(Duration timeout = Duration.max); 2105 2106 /++ 2107 Adds a delegate to be called on each loop iteration, called based on the `timingFlags`. 2108 2109 2110 The order in which the delegates are called is undefined and may change with each iteration of the loop. Additionally, when and how many times a loop iterates is undefined; multiple events might be handled by each iteration, or sometimes, nothing will be handled and it woke up spuriously. Your delegates need to be ok with all of this. 2111 2112 Parameters: 2113 dg = the delegate to call 2114 timingFlags = 2115 0: never actually run the function; it can assert error if you pass this 2116 1: run before each loop OS wait call 2117 2: run after each loop OS wait call 2118 3: run both before and after each OS wait call 2119 4: single shot? 2120 8: no-coalesce? (if after was just run, it will skip the before loops unless this flag is set) 2121 2122 +/ 2123 void addDelegateOnLoopIteration(void delegate() dg, uint timingFlags); 2124 2125 final void addDelegateOnLoopIteration(void function() dg, uint timingFlags) { 2126 addDelegateOnLoopIteration(toDelegate(dg), timingFlags); 2127 } 2128 2129 // to send messages between threads, i'll queue up a function that just call dispatchMessage. can embed the arg inside the callback helper prolly. 2130 // tho i might prefer to actually do messages w/ run payloads so it is easier to deduplicate i can still dedupe by insepcting the call args so idk 2131 2132 version(Posix) { 2133 @mustuse 2134 static struct UnregisterToken { 2135 private CoreEventLoopImplementation impl; 2136 private int fd; 2137 private CallbackHelper cb; 2138 2139 /++ 2140 Unregisters the file descriptor from the event loop and releases the reference to the callback held by the event loop (which will probably free it). 2141 2142 You must call this when you're done. Normally, this will be right before you close the fd (Which is often after the other side closes it, meaning you got a 0 length read). 2143 +/ 2144 void unregister() { 2145 assert(impl !is null, "Cannot reuse unregister token"); 2146 2147 version(Arsd_core_epoll) { 2148 impl.unregisterFd(fd); 2149 } else version(Arsd_core_kqueue) { 2150 // intentionally blank - all registrations are one-shot there 2151 // FIXME: actually it might not have gone off yet, in that case we do need to delete the filter 2152 } else version(EmptyCoreEvent) { 2153 2154 } 2155 else static assert(0); 2156 2157 cb.release(); 2158 this = typeof(this).init; 2159 } 2160 } 2161 2162 @mustuse 2163 static struct RearmToken { 2164 private bool readable; 2165 private CoreEventLoopImplementation impl; 2166 private int fd; 2167 private CallbackHelper cb; 2168 private uint flags; 2169 2170 /++ 2171 Calls [UnregisterToken.unregister] 2172 +/ 2173 void unregister() { 2174 assert(impl !is null, "cannot reuse rearm token after unregistering it"); 2175 2176 version(Arsd_core_epoll) { 2177 impl.unregisterFd(fd); 2178 } else version(Arsd_core_kqueue) { 2179 // intentionally blank - all registrations are one-shot there 2180 // FIXME: actually it might not have gone off yet, in that case we do need to delete the filter 2181 } else version(EmptyCoreEvent) { 2182 2183 } else static assert(0); 2184 2185 cb.release(); 2186 this = typeof(this).init; 2187 } 2188 2189 /++ 2190 Rearms the event so you will get another callback next time it is ready. 2191 +/ 2192 void rearm() { 2193 assert(impl !is null, "cannot reuse rearm token after unregistering it"); 2194 impl.rearmFd(this); 2195 } 2196 } 2197 2198 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb); 2199 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb); 2200 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb); 2201 } 2202 2203 version(Windows) { 2204 @mustuse 2205 static struct UnregisterToken { 2206 private CoreEventLoopImplementation impl; 2207 private HANDLE handle; 2208 private CallbackHelper cb; 2209 2210 /++ 2211 Unregisters the handle from the event loop and releases the reference to the callback held by the event loop (which will probably free it). 2212 2213 You must call this when you're done. Normally, this will be right before you close the handle. 2214 +/ 2215 void unregister() { 2216 assert(impl !is null, "Cannot reuse unregister token"); 2217 2218 impl.unregisterHandle(handle, cb); 2219 2220 cb.release(); 2221 this = typeof(this).init; 2222 } 2223 } 2224 2225 UnregisterToken addCallbackOnHandleReady(HANDLE handle, CallbackHelper cb); 2226 } 2227 } 2228 2229 /++ 2230 Get the event loop associated with this thread 2231 +/ 2232 ICoreEventLoop getThisThreadEventLoop(EventLoopType type = EventLoopType.AdHoc) { 2233 static ICoreEventLoop loop; 2234 if(loop is null) 2235 loop = new CoreEventLoopImplementation(); 2236 return loop; 2237 } 2238 2239 /++ 2240 The internal types that will be exposed through other api things. 2241 +/ 2242 package(arsd) enum EventLoopType { 2243 /++ 2244 The event loop is being run temporarily and the thread doesn't promise to keep running it. 2245 +/ 2246 AdHoc, 2247 /++ 2248 The event loop struct has been instantiated at top level. Its destructor will run when the 2249 function exits, which is only at the end of the entire block of work it is responsible for. 2250 2251 It must be in scope for the whole time the arsd event loop functions are expected to be used 2252 (meaning it should generally be top-level in `main`) 2253 +/ 2254 Explicit, 2255 /++ 2256 A specialization of `Explicit`, so all the same rules apply there, but this is specifically the event loop coming from simpledisplay or minigui. It will run for the duration of the UI's existence. 2257 +/ 2258 Ui, 2259 /++ 2260 A special event loop specifically for threads that listen to the task runner queue and handle I/O events from running tasks. Typically, a task runner runs cooperatively multitasked coroutines (so they prefer not to block the whole thread). 2261 +/ 2262 TaskRunner, 2263 /++ 2264 A special event loop specifically for threads that listen to the helper function request queue. Helper functions are expected to run independently for a somewhat long time (them blocking the thread for some time is normal) and send a reply message back to the requester. 2265 +/ 2266 HelperWorker 2267 } 2268 2269 /+ 2270 Tasks are given an object to talk to their parent... can be a dialog where it is like 2271 2272 sendBuffer 2273 waitForWordToProceed 2274 2275 in a loop 2276 2277 2278 Tasks are assigned to a worker thread and may share it with other tasks. 2279 +/ 2280 2281 2282 // the GC may not be able to see this! remember, it can be hidden inside kernel buffers 2283 version(HasThread) package(arsd) class CallbackHelper { 2284 import core.memory; 2285 2286 void call() { 2287 if(callback) 2288 callback(); 2289 } 2290 2291 void delegate() callback; 2292 void*[3] argsStore; 2293 2294 void addref() { 2295 atomicOp!"+="(refcount, 1); 2296 } 2297 2298 void release() { 2299 if(atomicOp!"-="(refcount, 1) <= 0) { 2300 if(flags & 1) 2301 GC.removeRoot(cast(void*) this); 2302 } 2303 } 2304 2305 private shared(int) refcount; 2306 private uint flags; 2307 2308 this(void function() callback) { 2309 this( () { callback(); } ); 2310 } 2311 2312 this(void delegate() callback, bool addRoot = true) { 2313 if(addRoot) { 2314 GC.addRoot(cast(void*) this); 2315 this.flags |= 1; 2316 } 2317 2318 this.addref(); 2319 this.callback = callback; 2320 } 2321 } 2322 2323 /++ 2324 This represents a file. Technically, file paths aren't actually strings (for example, on Linux, they need not be valid utf-8, while a D string is supposed to be), even though we almost always use them like that. 2325 2326 This type is meant to represent a filename / path. I might not keep it around. 2327 +/ 2328 struct FilePath { 2329 string path; 2330 2331 bool isNull() { 2332 return path is null; 2333 } 2334 2335 bool opCast(T:bool)() { 2336 return !isNull; 2337 } 2338 2339 string toString() { 2340 return path; 2341 } 2342 2343 //alias toString this; 2344 } 2345 2346 /++ 2347 Represents a generic async, waitable request. 2348 +/ 2349 class AsyncOperationRequest { 2350 /++ 2351 Actually issues the request, starting the operation. 2352 +/ 2353 abstract void start(); 2354 /++ 2355 Cancels the request. This will cause `isComplete` to return true once the cancellation has been processed, but [AsyncOperationResponse.wasSuccessful] will return `false` (unless it completed before the cancellation was processed, in which case it is still allowed to finish successfully). 2356 2357 After cancelling a request, you should still wait for it to complete to ensure that the task has actually released its resources before doing anything else on it. 2358 2359 Once a cancellation request has been sent, it cannot be undone. 2360 +/ 2361 abstract void cancel(); 2362 2363 /++ 2364 Returns `true` if the operation has been completed. It may be completed successfully, cancelled, or have errored out - to check this, call [waitForCompletion] and check the members on the response object. 2365 +/ 2366 abstract bool isComplete(); 2367 /++ 2368 Waits until the request has completed - successfully or otherwise - and returns the response object. It will run an ad-hoc event loop that may call other callbacks while waiting. 2369 2370 The response object may be embedded in the request object - do not reuse the request until you are finished with the response and do not keep the response around longer than you keep the request. 2371 2372 2373 Note to implementers: all subclasses should override this and return their specific response object. You can use the top-level `waitForFirstToCompleteByIndex` function with a single-element static array to help with the implementation. 2374 +/ 2375 abstract AsyncOperationResponse waitForCompletion(); 2376 2377 /++ 2378 2379 +/ 2380 // abstract void repeat(); 2381 } 2382 2383 /++ 2384 2385 +/ 2386 interface AsyncOperationResponse { 2387 /++ 2388 Returns true if the request completed successfully, finishing what it was supposed to. 2389 2390 Should be set to `false` if the request was cancelled before completing or encountered an error. 2391 +/ 2392 bool wasSuccessful(); 2393 } 2394 2395 /++ 2396 It returns the $(I request) so you can identify it more easily. `request.waitForCompletion()` is guaranteed to return the response without any actual wait, since it is already complete when this function returns. 2397 2398 Please note that "completion" is not necessary successful completion; a request being cancelled or encountering an error also counts as it being completed. 2399 2400 The `waitForFirstToCompleteByIndex` version instead returns the index of the array entry that completed first. 2401 2402 It is your responsibility to remove the completed request from the array before calling the function again, since any request already completed will always be immediately returned. 2403 2404 You might prefer using [asTheyComplete], which will give each request as it completes and loop over until all of them are complete. 2405 2406 Returns: 2407 `null` or `requests.length` if none completed before returning. 2408 +/ 2409 AsyncOperationRequest waitForFirstToComplete(AsyncOperationRequest[] requests...) { 2410 auto idx = waitForFirstToCompleteByIndex(requests); 2411 if(idx == requests.length) 2412 return null; 2413 return requests[idx]; 2414 } 2415 /// ditto 2416 size_t waitForFirstToCompleteByIndex(AsyncOperationRequest[] requests...) { 2417 size_t helper() { 2418 foreach(idx, request; requests) 2419 if(request.isComplete()) 2420 return idx; 2421 return requests.length; 2422 } 2423 2424 auto idx = helper(); 2425 // if one is already done, return it 2426 if(idx != requests.length) 2427 return idx; 2428 2429 // otherwise, run the ad-hoc event loop until one is 2430 // FIXME: what if we are inside a fiber? 2431 auto el = getThisThreadEventLoop(); 2432 el.run(() => (idx = helper()) != requests.length); 2433 2434 return idx; 2435 } 2436 2437 /++ 2438 Waits for all the `requests` to complete, giving each one through the range interface as it completes. 2439 2440 This meant to be used in a foreach loop. 2441 2442 The `requests` array and its contents must remain valid for the lifetime of the returned range. Its contents may be shuffled as the requests complete (the implementation works through an unstable sort+remove). 2443 +/ 2444 AsTheyCompleteRange asTheyComplete(AsyncOperationRequest[] requests...) { 2445 return AsTheyCompleteRange(requests); 2446 } 2447 /// ditto 2448 struct AsTheyCompleteRange { 2449 AsyncOperationRequest[] requests; 2450 2451 this(AsyncOperationRequest[] requests) { 2452 this.requests = requests; 2453 2454 if(requests.length == 0) 2455 return; 2456 2457 // wait for first one to complete, then move it to the front of the array 2458 moveFirstCompleteToFront(); 2459 } 2460 2461 private void moveFirstCompleteToFront() { 2462 auto idx = waitForFirstToCompleteByIndex(requests); 2463 2464 auto tmp = requests[0]; 2465 requests[0] = requests[idx]; 2466 requests[idx] = tmp; 2467 } 2468 2469 bool empty() { 2470 return requests.length == 0; 2471 } 2472 2473 void popFront() { 2474 assert(!empty); 2475 /+ 2476 this needs to 2477 1) remove the front of the array as being already processed (unless it is the initial priming call) 2478 2) wait for one of them to complete 2479 3) move the complete one to the front of the array 2480 +/ 2481 2482 requests[0] = requests[$-1]; 2483 requests = requests[0 .. $-1]; 2484 2485 if(requests.length) 2486 moveFirstCompleteToFront(); 2487 } 2488 2489 AsyncOperationRequest front() { 2490 return requests[0]; 2491 } 2492 } 2493 2494 version(Windows) { 2495 alias NativeFileHandle = HANDLE; /// 2496 alias NativeSocketHandle = SOCKET; /// 2497 alias NativePipeHandle = HANDLE; /// 2498 } else version(Posix) { 2499 alias NativeFileHandle = int; /// 2500 alias NativeSocketHandle = int; /// 2501 alias NativePipeHandle = int; /// 2502 } 2503 2504 /++ 2505 An `AbstractFile` represents a file handle on the operating system level. You cannot do much with it. 2506 +/ 2507 version(HasFile) class AbstractFile { 2508 private { 2509 NativeFileHandle handle; 2510 } 2511 2512 /++ 2513 +/ 2514 enum OpenMode { 2515 readOnly, /// C's "r", the file is read 2516 writeWithTruncation, /// C's "w", the file is blanked upon opening so it only holds what you write 2517 appendOnly, /// C's "a", writes will always be appended to the file 2518 readAndWrite /// C's "r+", writes will overwrite existing parts of the file based on where you seek (default is at the beginning) 2519 } 2520 2521 /++ 2522 +/ 2523 enum RequirePreexisting { 2524 no, 2525 yes 2526 } 2527 2528 /+ 2529 enum SpecialFlags { 2530 randomAccessExpected, /// FILE_FLAG_SEQUENTIAL_SCAN is turned off and posix_fadvise(POSIX_FADV_SEQUENTIAL) 2531 skipCache, /// O_DSYNC, FILE_FLAG_NO_BUFFERING and maybe WRITE_THROUGH. note that metadata still goes through the cache, FlushFileBuffers and fsync can still do those 2532 temporary, /// FILE_ATTRIBUTE_TEMPORARY on Windows, idk how to specify on linux. also FILE_FLAG_DELETE_ON_CLOSE can be combined to make a (almost) all memory file. kinda like a private anonymous mmap i believe. 2533 deleteWhenClosed, /// Windows has a flag for this but idk if it is of any real use 2534 async, /// open it in overlapped mode, all reads and writes must then provide an offset. Only implemented on Windows 2535 } 2536 +/ 2537 2538 /++ 2539 2540 +/ 2541 protected this(bool async, FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0) { 2542 version(Windows) { 2543 DWORD access; 2544 DWORD creation; 2545 2546 final switch(mode) { 2547 case OpenMode.readOnly: 2548 access = GENERIC_READ; 2549 creation = OPEN_EXISTING; 2550 break; 2551 case OpenMode.writeWithTruncation: 2552 access = GENERIC_WRITE; 2553 2554 final switch(require) { 2555 case RequirePreexisting.no: 2556 creation = CREATE_ALWAYS; 2557 break; 2558 case RequirePreexisting.yes: 2559 creation = TRUNCATE_EXISTING; 2560 break; 2561 } 2562 break; 2563 case OpenMode.appendOnly: 2564 access = FILE_APPEND_DATA; 2565 2566 final switch(require) { 2567 case RequirePreexisting.no: 2568 creation = CREATE_ALWAYS; 2569 break; 2570 case RequirePreexisting.yes: 2571 creation = OPEN_EXISTING; 2572 break; 2573 } 2574 break; 2575 case OpenMode.readAndWrite: 2576 access = GENERIC_READ | GENERIC_WRITE; 2577 2578 final switch(require) { 2579 case RequirePreexisting.no: 2580 creation = CREATE_NEW; 2581 break; 2582 case RequirePreexisting.yes: 2583 creation = OPEN_EXISTING; 2584 break; 2585 } 2586 break; 2587 } 2588 2589 WCharzBuffer wname = WCharzBuffer(filename.path); 2590 2591 auto handle = CreateFileW( 2592 wname.ptr, 2593 access, 2594 FILE_SHARE_READ, 2595 null, 2596 creation, 2597 FILE_ATTRIBUTE_NORMAL | (async ? FILE_FLAG_OVERLAPPED : 0), 2598 null 2599 ); 2600 2601 if(handle == INVALID_HANDLE_VALUE) { 2602 // FIXME: throw the filename and other params here too 2603 SavedArgument[3] args; 2604 args[0] = SavedArgument("filename", LimitedVariant(filename.path)); 2605 args[1] = SavedArgument("access", LimitedVariant(access, 2)); 2606 args[2] = SavedArgument("requirePreexisting", LimitedVariant(require == RequirePreexisting.yes)); 2607 throw new WindowsApiException("CreateFileW", GetLastError(), args[]); 2608 } 2609 2610 this.handle = handle; 2611 } else version(Posix) { 2612 import core.sys.posix.unistd; 2613 import core.sys.posix.fcntl; 2614 2615 CharzBuffer namez = CharzBuffer(filename.path); 2616 int flags; 2617 2618 // FIXME does mac not have cloexec for real or is this just a druntime problem????? 2619 version(Arsd_core_has_cloexec) { 2620 flags = O_CLOEXEC; 2621 } else { 2622 scope(success) 2623 setCloExec(this.handle); 2624 } 2625 2626 if(async) 2627 flags |= O_NONBLOCK; 2628 2629 final switch(mode) { 2630 case OpenMode.readOnly: 2631 flags |= O_RDONLY; 2632 break; 2633 case OpenMode.writeWithTruncation: 2634 flags |= O_WRONLY | O_TRUNC; 2635 2636 final switch(require) { 2637 case RequirePreexisting.no: 2638 flags |= O_CREAT; 2639 break; 2640 case RequirePreexisting.yes: 2641 break; 2642 } 2643 break; 2644 case OpenMode.appendOnly: 2645 flags |= O_APPEND; 2646 2647 final switch(require) { 2648 case RequirePreexisting.no: 2649 flags |= O_CREAT; 2650 break; 2651 case RequirePreexisting.yes: 2652 break; 2653 } 2654 break; 2655 case OpenMode.readAndWrite: 2656 flags |= O_RDWR; 2657 2658 final switch(require) { 2659 case RequirePreexisting.no: 2660 flags |= O_CREAT; 2661 break; 2662 case RequirePreexisting.yes: 2663 break; 2664 } 2665 break; 2666 } 2667 2668 auto perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; 2669 int fd = open(namez.ptr, flags, perms); 2670 if(fd == -1) { 2671 SavedArgument[3] args; 2672 args[0] = SavedArgument("filename", LimitedVariant(filename.path)); 2673 args[1] = SavedArgument("flags", LimitedVariant(flags, 2)); 2674 args[2] = SavedArgument("perms", LimitedVariant(perms, 8)); 2675 throw new ErrnoApiException("open", errno, args[]); 2676 } 2677 2678 this.handle = fd; 2679 } 2680 } 2681 2682 /++ 2683 2684 +/ 2685 private this(NativeFileHandle handleToWrap) { 2686 this.handle = handleToWrap; 2687 } 2688 2689 // only available on some types of file 2690 long size() { return 0; } 2691 2692 // note that there is no fsync thing, instead use the special flag. 2693 2694 /++ 2695 2696 +/ 2697 void close() { 2698 version(Windows) { 2699 Win32Enforce!CloseHandle(handle); 2700 handle = null; 2701 } else version(Posix) { 2702 import unix = core.sys.posix.unistd; 2703 import core.sys.posix.fcntl; 2704 2705 ErrnoEnforce!(unix.close)(handle); 2706 handle = -1; 2707 } 2708 } 2709 } 2710 2711 /++ 2712 2713 +/ 2714 version(HasFile) class File : AbstractFile { 2715 2716 /++ 2717 Opens a file in synchronous access mode. 2718 2719 The permission mask is on used on posix systems FIXME: implement it 2720 +/ 2721 this(FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0, uint permMask = 0) { 2722 super(false, filename, mode, require, specialFlags); 2723 } 2724 2725 /++ 2726 2727 +/ 2728 ubyte[] read(scope ubyte[] buffer) { 2729 return null; 2730 } 2731 2732 /++ 2733 2734 +/ 2735 void write(in void[] buffer) { 2736 } 2737 2738 enum Seek { 2739 current, 2740 fromBeginning, 2741 fromEnd 2742 } 2743 2744 // Seeking/telling/sizing is not permitted when appending and some files don't support it 2745 // also not permitted in async mode 2746 void seek(long where, Seek fromWhence) {} 2747 long tell() { return 0; } 2748 } 2749 2750 /++ 2751 Only one operation can be pending at any time in the current implementation. 2752 +/ 2753 version(HasFile) class AsyncFile : AbstractFile { 2754 /++ 2755 Opens a file in asynchronous access mode. 2756 +/ 2757 this(FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0, uint permissionMask = 0) { 2758 // FIXME: implement permissionMask 2759 super(true, filename, mode, require, specialFlags); 2760 } 2761 2762 package(arsd) this(NativeFileHandle adoptPreSetup) { 2763 super(adoptPreSetup); 2764 } 2765 2766 /// 2767 AsyncReadRequest read(ubyte[] buffer, long offset = 0) { 2768 return new AsyncReadRequest(this, buffer, offset); 2769 } 2770 2771 /// 2772 AsyncWriteRequest write(const(void)[] buffer, long offset = 0) { 2773 return new AsyncWriteRequest(this, cast(ubyte[]) buffer, offset); 2774 } 2775 2776 } 2777 2778 /++ 2779 Reads or writes a file in one call. It might internally yield, but is generally blocking if it returns values. The callback ones depend on the implementation. 2780 2781 Tip: prefer the callback ones. If settings where async is possible, it will do async, and if not, it will sync. 2782 2783 NOT IMPLEMENTED 2784 +/ 2785 void writeFile(string filename, const(void)[] contents) { 2786 2787 } 2788 2789 /// ditto 2790 string readTextFile(string filename, string fileEncoding = null) { 2791 return null; 2792 } 2793 2794 /// ditto 2795 const(ubyte[]) readBinaryFile(string filename) { 2796 return null; 2797 } 2798 2799 /+ 2800 private Class recycleObject(Class, Args...)(Class objectToRecycle, Args args) { 2801 if(objectToRecycle is null) 2802 return new Class(args); 2803 // destroy nulls out the vtable which is the first thing in the object 2804 // so if it hasn't already been destroyed, we'll do it here 2805 if((*cast(void**) objectToRecycle) !is null) { 2806 assert(typeid(objectToRecycle) is typeid(Class)); // to make sure we're actually recycling the right kind of object 2807 .destroy(objectToRecycle); 2808 } 2809 2810 // then go ahead and reinitialize it 2811 ubyte[] rawData = (cast(ubyte*) cast(void*) objectToRecycle)[0 .. __traits(classInstanceSize, Class)]; 2812 rawData[] = (cast(ubyte[]) typeid(Class).initializer)[]; 2813 2814 objectToRecycle.__ctor(args); 2815 2816 return objectToRecycle; 2817 } 2818 +/ 2819 2820 /+ 2821 /++ 2822 Preallocates a class object without initializing it. 2823 2824 This is suitable *only* for passing to one of the functions in here that takes a preallocated object for recycling. 2825 +/ 2826 Class preallocate(Class)() { 2827 import core.memory; 2828 // FIXME: can i pass NO_SCAN here? 2829 return cast(Class) GC.calloc(__traits(classInstanceSize, Class), 0, typeid(Class)); 2830 } 2831 2832 OwnedClass!Class preallocateOnStack(Class)() { 2833 2834 } 2835 +/ 2836 2837 // thanks for a random person on stack overflow for this function 2838 version(Windows) 2839 BOOL MyCreatePipeEx( 2840 PHANDLE lpReadPipe, 2841 PHANDLE lpWritePipe, 2842 LPSECURITY_ATTRIBUTES lpPipeAttributes, 2843 DWORD nSize, 2844 DWORD dwReadMode, 2845 DWORD dwWriteMode 2846 ) 2847 { 2848 HANDLE ReadPipeHandle, WritePipeHandle; 2849 DWORD dwError; 2850 CHAR[MAX_PATH] PipeNameBuffer; 2851 2852 if (nSize == 0) { 2853 nSize = 4096; 2854 } 2855 2856 // FIXME: should be atomic op and gshared 2857 static shared(int) PipeSerialNumber = 0; 2858 2859 import core.stdc.string; 2860 import core.stdc.stdio; 2861 2862 sprintf(PipeNameBuffer.ptr, 2863 "\\\\.\\Pipe\\ArsdCoreAnonymousPipe.%08x.%08x".ptr, 2864 GetCurrentProcessId(), 2865 atomicOp!"+="(PipeSerialNumber, 1) 2866 ); 2867 2868 ReadPipeHandle = CreateNamedPipeA( 2869 PipeNameBuffer.ptr, 2870 1/*PIPE_ACCESS_INBOUND*/ | dwReadMode, 2871 0/*PIPE_TYPE_BYTE*/ | 0/*PIPE_WAIT*/, 2872 1, // Number of pipes 2873 nSize, // Out buffer size 2874 nSize, // In buffer size 2875 120 * 1000, // Timeout in ms 2876 lpPipeAttributes 2877 ); 2878 2879 if (! ReadPipeHandle) { 2880 return FALSE; 2881 } 2882 2883 WritePipeHandle = CreateFileA( 2884 PipeNameBuffer.ptr, 2885 GENERIC_WRITE, 2886 0, // No sharing 2887 lpPipeAttributes, 2888 OPEN_EXISTING, 2889 FILE_ATTRIBUTE_NORMAL | dwWriteMode, 2890 null // Template file 2891 ); 2892 2893 if (INVALID_HANDLE_VALUE == WritePipeHandle) { 2894 dwError = GetLastError(); 2895 CloseHandle( ReadPipeHandle ); 2896 SetLastError(dwError); 2897 return FALSE; 2898 } 2899 2900 *lpReadPipe = ReadPipeHandle; 2901 *lpWritePipe = WritePipeHandle; 2902 return( TRUE ); 2903 } 2904 2905 2906 2907 /+ 2908 2909 // this is probably useless. 2910 2911 /++ 2912 Creates a pair of anonymous pipes ready for async operations. 2913 2914 You can pass some preallocated objects to recycle if you like. 2915 +/ 2916 AsyncAnonymousPipe[2] anonymousPipePair(AsyncAnonymousPipe[2] preallocatedObjects = [null, null], bool inheritable = false) { 2917 version(Posix) { 2918 int[2] fds; 2919 auto ret = pipe(fds); 2920 2921 if(ret == -1) 2922 throw new SystemApiException("pipe", errno); 2923 2924 // FIXME: do we want them inheritable? and do we want both sides to be async? 2925 if(!inheritable) { 2926 setCloExec(fds[0]); 2927 setCloExec(fds[1]); 2928 } 2929 // if it is inherited, do we actually want it non-blocking? 2930 makeNonBlocking(fds[0]); 2931 makeNonBlocking(fds[1]); 2932 2933 return [ 2934 recycleObject(preallocatedObjects[0], fds[0]), 2935 recycleObject(preallocatedObjects[1], fds[1]), 2936 ]; 2937 } else version(Windows) { 2938 HANDLE rp, wp; 2939 // FIXME: do we want them inheritable? and do we want both sides to be async? 2940 if(!MyCreatePipeEx(&rp, &wp, null, 0, FILE_FLAG_OVERLAPPED, FILE_FLAG_OVERLAPPED)) 2941 throw new SystemApiException("MyCreatePipeEx", GetLastError()); 2942 return [ 2943 recycleObject(preallocatedObjects[0], rp), 2944 recycleObject(preallocatedObjects[1], wp), 2945 ]; 2946 } else throw ArsdException!"NotYetImplemented"(); 2947 } 2948 // on posix, just do pipe() w/ non block 2949 // on windows, do an overlapped named pipe server, connect, stop listening, return pair. 2950 +/ 2951 2952 /+ 2953 class NamedPipe : AsyncFile { 2954 2955 } 2956 +/ 2957 2958 /++ 2959 A named pipe ready to accept connections. 2960 2961 A Windows named pipe is an IPC mechanism usable on local machines or across a Windows network. 2962 +/ 2963 version(Windows) 2964 class NamedPipeServer { 2965 // unix domain socket or windows named pipe 2966 2967 // Promise!AsyncAnonymousPipe connect; 2968 // Promise!AsyncAnonymousPipe accept; 2969 2970 // when a new connection arrives, it calls your callback 2971 // can be on a specific thread or on any thread 2972 } 2973 2974 private version(Windows) extern(Windows) { 2975 const(char)* inet_ntop(int, const void*, char*, socklen_t); 2976 } 2977 2978 /++ 2979 Some functions that return arrays allow you to provide your own buffer. These are indicated in the type system as `UserProvidedBuffer!Type`, and you get to decide what you want to happen if the buffer is too small via the [OnOutOfSpace] parameter. 2980 2981 These are usually optional, since an empty user provided buffer with the default policy of reallocate will also work fine for whatever needs to be returned, thanks to the garbage collector taking care of it for you. 2982 2983 The API inside `UserProvidedBuffer` is all private to the arsd library implementation; your job is just to provide the buffer to it with [provideBuffer] or a constructor call and decide on your on-out-of-space policy. 2984 2985 $(TIP 2986 To properly size a buffer, I suggest looking at what covers about 80% of cases. Trying to cover everything often leads to wasted buffer space, and if you use a reallocate policy it can cover the rest. You might be surprised how far just two elements can go! 2987 ) 2988 2989 History: 2990 Added August 4, 2023 (dub v11.0) 2991 +/ 2992 struct UserProvidedBuffer(T) { 2993 private T[] buffer; 2994 private int actualLength; 2995 private OnOutOfSpace policy; 2996 2997 /++ 2998 2999 +/ 3000 public this(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate) { 3001 this.buffer = buffer; 3002 this.policy = policy; 3003 } 3004 3005 package(arsd) bool append(T item) { 3006 if(actualLength < buffer.length) { 3007 buffer[actualLength++] = item; 3008 return true; 3009 } else final switch(policy) { 3010 case OnOutOfSpace.discard: 3011 return false; 3012 case OnOutOfSpace.exception: 3013 throw ArsdException!"Buffer out of space"(buffer.length, actualLength); 3014 case OnOutOfSpace.reallocate: 3015 buffer ~= item; 3016 actualLength++; 3017 return true; 3018 } 3019 } 3020 3021 package(arsd) T[] slice() return { 3022 return buffer[0 .. actualLength]; 3023 } 3024 } 3025 3026 /// ditto 3027 UserProvidedBuffer!T provideBuffer(T)(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate) { 3028 return UserProvidedBuffer!T(buffer, policy); 3029 } 3030 3031 /++ 3032 Possible policies for [UserProvidedBuffer]s that run out of space. 3033 +/ 3034 enum OnOutOfSpace { 3035 reallocate, /// reallocate the buffer with the GC to make room 3036 discard, /// discard all contents that do not fit in your provided buffer 3037 exception, /// throw an exception if there is data that would not fit in your provided buffer 3038 } 3039 3040 3041 /++ 3042 For functions that give you an unknown address, you can use this to hold it. 3043 3044 Can get: 3045 ip4 3046 ip6 3047 unix 3048 abstract_ 3049 3050 name lookup for connect (stream or dgram) 3051 request canonical name? 3052 3053 interface lookup for bind (stream or dgram) 3054 +/ 3055 version(HasSocket) struct SocketAddress { 3056 import core.sys.posix.netdb; 3057 3058 /++ 3059 Provides the set of addresses to listen on all supported protocols on the machine for the given interfaces. `localhost` only listens on the loopback interface, whereas `allInterfaces` will listen on loopback as well as the others on the system (meaning it may be publicly exposed to the internet). 3060 3061 If you provide a buffer, I recommend using one of length two, so `SocketAddress[2]`, since this usually provides one address for ipv4 and one for ipv6. 3062 +/ 3063 static SocketAddress[] localhost(ushort port, return UserProvidedBuffer!SocketAddress buffer = null) { 3064 buffer.append(ip6("::1", port)); 3065 buffer.append(ip4("127.0.0.1", port)); 3066 return buffer.slice; 3067 } 3068 3069 /// ditto 3070 static SocketAddress[] allInterfaces(ushort port, return UserProvidedBuffer!SocketAddress buffer = null) { 3071 char[16] str; 3072 return allInterfaces(intToString(port, str[]), buffer); 3073 } 3074 3075 /// ditto 3076 static SocketAddress[] allInterfaces(scope const char[] serviceOrPort, return UserProvidedBuffer!SocketAddress buffer = null) { 3077 addrinfo hints; 3078 hints.ai_flags = AI_PASSIVE; 3079 hints.ai_socktype = SOCK_STREAM; // just to filter it down a little tbh 3080 return get(null, serviceOrPort, &hints, buffer); 3081 } 3082 3083 /++ 3084 Returns a single address object for the given protocol and parameters. 3085 3086 You probably should generally prefer [get], [localhost], or [allInterfaces] to have more flexible code. 3087 +/ 3088 static SocketAddress ip4(scope const char[] address, ushort port, bool forListening = false) { 3089 return getSingleAddress(AF_INET, AI_NUMERICHOST | (forListening ? AI_PASSIVE : 0), address, port); 3090 } 3091 3092 /// ditto 3093 static SocketAddress ip4(ushort port) { 3094 return ip4(null, port, true); 3095 } 3096 3097 /// ditto 3098 static SocketAddress ip6(scope const char[] address, ushort port, bool forListening = false) { 3099 return getSingleAddress(AF_INET6, AI_NUMERICHOST | (forListening ? AI_PASSIVE : 0), address, port); 3100 } 3101 3102 /// ditto 3103 static SocketAddress ip6(ushort port) { 3104 return ip6(null, port, true); 3105 } 3106 3107 /// ditto 3108 static SocketAddress unix(scope const char[] path) { 3109 // FIXME 3110 SocketAddress addr; 3111 return addr; 3112 } 3113 3114 /// ditto 3115 static SocketAddress abstract_(scope const char[] path) { 3116 char[190] buffer = void; 3117 buffer[0] = 0; 3118 buffer[1 .. path.length] = path[]; 3119 return unix(buffer[0 .. 1 + path.length]); 3120 } 3121 3122 private static SocketAddress getSingleAddress(int family, int flags, scope const char[] address, ushort port) { 3123 addrinfo hints; 3124 hints.ai_family = family; 3125 hints.ai_flags = flags; 3126 3127 char[16] portBuffer; 3128 char[] portString = intToString(port, portBuffer[]); 3129 3130 SocketAddress[1] addr; 3131 auto res = get(address, portString, &hints, provideBuffer(addr[])); 3132 if(res.length == 0) 3133 throw ArsdException!"bad address"(address.idup, port); 3134 return res[0]; 3135 } 3136 3137 /++ 3138 Calls `getaddrinfo` and returns the array of results. It will populate the data into the buffer you provide, if you provide one, otherwise it will allocate its own. 3139 +/ 3140 static SocketAddress[] get(scope const char[] nodeName, scope const char[] serviceOrPort, addrinfo* hints = null, return UserProvidedBuffer!SocketAddress buffer = null, scope bool delegate(scope addrinfo* ai) filter = null) @trusted { 3141 addrinfo* res; 3142 CharzBuffer node = nodeName; 3143 CharzBuffer service = serviceOrPort; 3144 auto ret = getaddrinfo(nodeName is null ? null : node.ptr, serviceOrPort is null ? null : service.ptr, hints, &res); 3145 if(ret == 0) { 3146 auto current = res; 3147 while(current) { 3148 if(filter is null || filter(current)) { 3149 SocketAddress addr; 3150 addr.addrlen = cast(socklen_t) current.ai_addrlen; 3151 switch(current.ai_family) { 3152 case AF_INET: 3153 addr.in4 = * cast(sockaddr_in*) current.ai_addr; 3154 break; 3155 case AF_INET6: 3156 addr.in6 = * cast(sockaddr_in6*) current.ai_addr; 3157 break; 3158 case AF_UNIX: 3159 addr.unix_address = * cast(sockaddr_un*) current.ai_addr; 3160 break; 3161 default: 3162 // skip 3163 } 3164 3165 if(!buffer.append(addr)) 3166 break; 3167 } 3168 3169 current = current.ai_next; 3170 } 3171 3172 freeaddrinfo(res); 3173 } else { 3174 version(Windows) { 3175 throw new WindowsApiException("getaddrinfo", ret); 3176 } else { 3177 const char* error = gai_strerror(ret); 3178 } 3179 } 3180 3181 return buffer.slice; 3182 } 3183 3184 /++ 3185 Returns a string representation of the address that identifies it in a custom format. 3186 3187 $(LIST 3188 * Unix domain socket addresses are their path prefixed with "unix:", unless they are in the abstract namespace, in which case it is prefixed with "abstract:" and the zero is trimmed out. For example, "unix:/tmp/pipe". 3189 3190 * IPv4 addresses are written in dotted decimal followed by a colon and the port number. For example, "127.0.0.1:8080". 3191 3192 * IPv6 addresses are written in colon separated hex format, but enclosed in brackets, then followed by the colon and port number. For example, "[::1]:8080". 3193 ) 3194 +/ 3195 string toString() const @trusted { 3196 char[200] buffer; 3197 switch(address.sa_family) { 3198 case AF_INET: 3199 auto writable = stringz(inet_ntop(address.sa_family, &in4.sin_addr, buffer.ptr, buffer.length)); 3200 auto it = writable.borrow; 3201 buffer[it.length] = ':'; 3202 auto numbers = intToString(port, buffer[it.length + 1 .. $]); 3203 return buffer[0 .. it.length + 1 + numbers.length].idup; 3204 case AF_INET6: 3205 buffer[0] = '['; 3206 auto writable = stringz(inet_ntop(address.sa_family, &in6.sin6_addr, buffer.ptr + 1, buffer.length - 1)); 3207 auto it = writable.borrow; 3208 buffer[it.length + 1] = ']'; 3209 buffer[it.length + 2] = ':'; 3210 auto numbers = intToString(port, buffer[it.length + 3 .. $]); 3211 return buffer[0 .. it.length + 3 + numbers.length].idup; 3212 case AF_UNIX: 3213 // FIXME: it might be abstract in which case stringz is wrong!!!!! 3214 auto writable = stringz(cast(char*) unix_address.sun_path.ptr).borrow; 3215 if(writable.length == 0) 3216 return "unix:"; 3217 string prefix = writable[0] == 0 ? "abstract:" : "unix:"; 3218 buffer[0 .. prefix.length] = prefix[]; 3219 buffer[prefix.length .. prefix.length + writable.length] = writable[writable[0] == 0 ? 1 : 0 .. $]; 3220 return buffer.idup; 3221 case AF_UNSPEC: 3222 return "<unspecified address>"; 3223 default: 3224 return "<unsupported address>"; // FIXME 3225 } 3226 } 3227 3228 ushort port() const @trusted { 3229 switch(address.sa_family) { 3230 case AF_INET: 3231 return ntohs(in4.sin_port); 3232 case AF_INET6: 3233 return ntohs(in6.sin6_port); 3234 default: 3235 return 0; 3236 } 3237 } 3238 3239 /+ 3240 @safe unittest { 3241 SocketAddress[4] buffer; 3242 foreach(addr; SocketAddress.get("arsdnet.net", "http", null, provideBuffer(buffer[]))) 3243 writeln(addr.toString()); 3244 } 3245 +/ 3246 3247 /+ 3248 unittest { 3249 // writeln(SocketAddress.ip4(null, 4444, true)); 3250 // writeln(SocketAddress.ip4("400.3.2.1", 4444)); 3251 // writeln(SocketAddress.ip4("bar", 4444)); 3252 foreach(addr; localhost(4444)) 3253 writeln(addr.toString()); 3254 } 3255 +/ 3256 3257 socklen_t addrlen = typeof(this).sizeof - socklen_t.sizeof; // the size of the union below 3258 3259 union { 3260 sockaddr address; 3261 3262 sockaddr_storage storage; 3263 3264 sockaddr_in in4; 3265 sockaddr_in6 in6; 3266 3267 sockaddr_un unix_address; 3268 } 3269 3270 /+ 3271 this(string node, string serviceOrPort, int family = 0) { 3272 // need to populate the approrpiate address and the length and make sure you set sa_family 3273 } 3274 +/ 3275 3276 int domain() { 3277 return address.sa_family; 3278 } 3279 sockaddr* rawAddr() return { 3280 return &address; 3281 } 3282 socklen_t rawAddrLength() { 3283 return addrlen; 3284 } 3285 3286 // FIXME it is AF_BLUETOOTH 3287 // see: https://people.csail.mit.edu/albert/bluez-intro/x79.html 3288 // see: https://learn.microsoft.com/en-us/windows/win32/Bluetooth/bluetooth-programming-with-windows-sockets 3289 } 3290 3291 private version(Windows) { 3292 struct sockaddr_un { 3293 ushort sun_family; 3294 char[108] sun_path; 3295 } 3296 } 3297 3298 version(HasFile) class AsyncSocket : AsyncFile { 3299 // otherwise: accept, bind, connect, shutdown, close. 3300 3301 static auto lastError() { 3302 version(Windows) 3303 return WSAGetLastError(); 3304 else 3305 return errno; 3306 } 3307 3308 static bool wouldHaveBlocked() { 3309 auto error = lastError; 3310 version(Windows) { 3311 return error == WSAEWOULDBLOCK || error == WSAETIMEDOUT; 3312 } else { 3313 return error == EAGAIN || error == EWOULDBLOCK; 3314 } 3315 } 3316 3317 version(Windows) 3318 enum INVALID = INVALID_SOCKET; 3319 else 3320 enum INVALID = -1; 3321 3322 // type is mostly SOCK_STREAM or SOCK_DGRAM 3323 /++ 3324 Creates a socket compatible with the given address. It does not actually connect or bind, nor store the address. You will want to pass it again to those functions: 3325 3326 --- 3327 auto socket = new Socket(address, Socket.Type.Stream); 3328 socket.connect(address).waitForCompletion(); 3329 --- 3330 +/ 3331 this(SocketAddress address, int type, int protocol = 0) { 3332 // need to look up these values for linux 3333 // type |= SOCK_NONBLOCK | SOCK_CLOEXEC; 3334 3335 handle_ = socket(address.domain(), type, protocol); 3336 if(handle == INVALID) 3337 throw new SystemApiException("socket", lastError()); 3338 3339 super(cast(NativeFileHandle) handle); // I think that cast is ok on Windows... i think 3340 3341 version(Posix) { 3342 makeNonBlocking(handle); 3343 setCloExec(handle); 3344 } 3345 3346 if(address.domain == AF_INET6) { 3347 int opt = 1; 3348 setsockopt(handle, IPPROTO_IPV6 /*SOL_IPV6*/, IPV6_V6ONLY, &opt, opt.sizeof); 3349 } 3350 3351 // FIXME: chekc for broadcast 3352 3353 // FIXME: REUSEADDR ? 3354 3355 // FIXME: also set NO_DELAY prolly 3356 // int opt = 1; 3357 // setsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof); 3358 } 3359 3360 /++ 3361 Enabling NODELAY can give latency improvements if you are managing buffers on your end 3362 +/ 3363 void setNoDelay(bool enabled) { 3364 3365 } 3366 3367 /++ 3368 3369 `allowQuickRestart` will set the SO_REUSEADDR on unix and SO_DONTLINGER on Windows, 3370 allowing the application to be quickly restarted despite there still potentially being 3371 pending data in the tcp stack. 3372 3373 See https://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux for more information. 3374 3375 If you already set your appropriate socket options or value correctness and reliability of the network stream over restart speed, leave this at the default `false`. 3376 +/ 3377 void bind(SocketAddress address, bool allowQuickRestart = false) { 3378 if(allowQuickRestart) { 3379 // FIXME 3380 } 3381 3382 auto ret = .bind(handle, address.rawAddr, address.rawAddrLength); 3383 if(ret == -1) 3384 throw new SystemApiException("bind", lastError); 3385 } 3386 3387 /++ 3388 You must call [bind] before this. 3389 3390 The backlog should be set to a value where your application can reliably catch up on the backlog in a reasonable amount of time under average load. It is meant to smooth over short duration bursts and making it too big will leave clients hanging - which might cause them to try to reconnect, thinking things got lost in transit, adding to your impossible backlog. 3391 3392 I personally tend to set this to be two per worker thread unless I have actual real world measurements saying to do something else. It is a bit arbitrary and not based on legitimate reasoning, it just seems to work for me (perhaps just because it has never really been put to the test). 3393 +/ 3394 void listen(int backlog) { 3395 auto ret = .listen(handle, backlog); 3396 if(ret == -1) 3397 throw new SystemApiException("listen", lastError); 3398 } 3399 3400 /++ 3401 +/ 3402 void shutdown(int how) { 3403 auto ret = .shutdown(handle, how); 3404 if(ret == -1) 3405 throw new SystemApiException("shutdown", lastError); 3406 } 3407 3408 /++ 3409 +/ 3410 override void close() { 3411 version(Windows) 3412 closesocket(handle); 3413 else 3414 .close(handle); 3415 handle_ = -1; 3416 } 3417 3418 /++ 3419 You can also construct your own request externally to control the memory more. 3420 +/ 3421 AsyncConnectRequest connect(SocketAddress address, ubyte[] bufferToSend = null) { 3422 return new AsyncConnectRequest(this, address, bufferToSend); 3423 } 3424 3425 /++ 3426 You can also construct your own request externally to control the memory more. 3427 +/ 3428 AsyncAcceptRequest accept() { 3429 return new AsyncAcceptRequest(this); 3430 } 3431 3432 // note that send is just sendto w/ a null address 3433 // and receive is just receivefrom w/ a null address 3434 /++ 3435 You can also construct your own request externally to control the memory more. 3436 +/ 3437 AsyncSendRequest send(const(ubyte)[] buffer, int flags = 0) { 3438 return new AsyncSendRequest(this, buffer, null, flags); 3439 } 3440 3441 /++ 3442 You can also construct your own request externally to control the memory more. 3443 +/ 3444 AsyncReceiveRequest receive(ubyte[] buffer, int flags = 0) { 3445 return new AsyncReceiveRequest(this, buffer, null, flags); 3446 } 3447 3448 /++ 3449 You can also construct your own request externally to control the memory more. 3450 +/ 3451 AsyncSendRequest sendTo(const(ubyte)[] buffer, SocketAddress* address, int flags = 0) { 3452 return new AsyncSendRequest(this, buffer, address, flags); 3453 } 3454 /++ 3455 You can also construct your own request externally to control the memory more. 3456 +/ 3457 AsyncReceiveRequest receiveFrom(ubyte[] buffer, SocketAddress* address, int flags = 0) { 3458 return new AsyncReceiveRequest(this, buffer, address, flags); 3459 } 3460 3461 /++ 3462 +/ 3463 SocketAddress localAddress() { 3464 SocketAddress addr; 3465 getsockname(handle, &addr.address, &addr.addrlen); 3466 return addr; 3467 } 3468 /++ 3469 +/ 3470 SocketAddress peerAddress() { 3471 SocketAddress addr; 3472 getpeername(handle, &addr.address, &addr.addrlen); 3473 return addr; 3474 } 3475 3476 // for unix sockets on unix only: send/receive fd, get peer creds 3477 3478 /++ 3479 3480 +/ 3481 final NativeSocketHandle handle() { 3482 return handle_; 3483 } 3484 3485 private NativeSocketHandle handle_; 3486 } 3487 3488 /++ 3489 Initiates a connection request and optionally sends initial data as soon as possible. 3490 3491 Calls `ConnectEx` on Windows and emulates it on other systems. 3492 3493 The entire buffer is sent before the operation is considered complete. 3494 3495 NOT IMPLEMENTED / NOT STABLE 3496 +/ 3497 version(HasSocket) class AsyncConnectRequest : AsyncOperationRequest { 3498 // FIXME: i should take a list of addresses and take the first one that succeeds, so a getaddrinfo can be sent straight in. 3499 this(AsyncSocket socket, SocketAddress address, ubyte[] dataToWrite) { 3500 3501 } 3502 3503 override void start() {} 3504 override void cancel() {} 3505 override bool isComplete() { return true; } 3506 override AsyncConnectResponse waitForCompletion() { assert(0); } 3507 } 3508 /++ 3509 +/ 3510 version(HasSocket) class AsyncConnectResponse : AsyncOperationResponse { 3511 const SystemErrorCode errorCode; 3512 3513 this(SystemErrorCode errorCode) { 3514 this.errorCode = errorCode; 3515 } 3516 3517 override bool wasSuccessful() { 3518 return errorCode.wasSuccessful; 3519 } 3520 3521 } 3522 3523 // FIXME: TransmitFile/sendfile support 3524 3525 /++ 3526 Calls `AcceptEx` on Windows and emulates it on other systems. 3527 3528 NOT IMPLEMENTED / NOT STABLE 3529 +/ 3530 version(HasSocket) class AsyncAcceptRequest : AsyncOperationRequest { 3531 AsyncSocket socket; 3532 3533 override void start() {} 3534 override void cancel() {} 3535 override bool isComplete() { return true; } 3536 override AsyncConnectResponse waitForCompletion() { assert(0); } 3537 3538 3539 struct LowLevelOperation { 3540 AsyncSocket file; 3541 ubyte[] buffer; 3542 SocketAddress* address; 3543 3544 this(typeof(this.tupleof) args) { 3545 this.tupleof = args; 3546 } 3547 3548 version(Windows) { 3549 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 3550 WSABUF buf; 3551 buf.len = cast(int) buffer.length; 3552 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 3553 3554 uint flags; 3555 3556 if(address is null) 3557 return WSARecv(file.handle, &buf, 1, null, &flags, overlapped, ocr); 3558 else { 3559 return WSARecvFrom(file.handle, &buf, 1, null, &flags, &(address.address), &(address.addrlen), overlapped, ocr); 3560 } 3561 } 3562 } else { 3563 auto opCall() { 3564 int flags; 3565 if(address is null) 3566 return core.sys.posix.sys.socket.recv(file.handle, buffer.ptr, buffer.length, flags); 3567 else 3568 return core.sys.posix.sys.socket.recvfrom(file.handle, buffer.ptr, buffer.length, flags, &(address.address), &(address.addrlen)); 3569 } 3570 } 3571 3572 string errorString() { 3573 return "Receive"; 3574 } 3575 } 3576 mixin OverlappedIoRequest!(AsyncAcceptResponse, LowLevelOperation); 3577 3578 this(AsyncSocket socket, ubyte[] buffer = null, SocketAddress* address = null) { 3579 llo = LowLevelOperation(socket, buffer, address); 3580 this.response = typeof(this.response).defaultConstructed; 3581 } 3582 3583 // can also look up the local address 3584 } 3585 /++ 3586 +/ 3587 version(HasSocket) class AsyncAcceptResponse : AsyncOperationResponse { 3588 AsyncSocket newSocket; 3589 const SystemErrorCode errorCode; 3590 3591 this(SystemErrorCode errorCode, ubyte[] buffer) { 3592 this.errorCode = errorCode; 3593 } 3594 3595 this(AsyncSocket newSocket, SystemErrorCode errorCode) { 3596 this.newSocket = newSocket; 3597 this.errorCode = errorCode; 3598 } 3599 3600 override bool wasSuccessful() { 3601 return errorCode.wasSuccessful; 3602 } 3603 } 3604 3605 /++ 3606 +/ 3607 version(HasSocket) class AsyncReceiveRequest : AsyncOperationRequest { 3608 struct LowLevelOperation { 3609 AsyncSocket file; 3610 ubyte[] buffer; 3611 int flags; 3612 SocketAddress* address; 3613 3614 this(typeof(this.tupleof) args) { 3615 this.tupleof = args; 3616 } 3617 3618 version(Windows) { 3619 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 3620 WSABUF buf; 3621 buf.len = cast(int) buffer.length; 3622 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 3623 3624 uint flags = this.flags; 3625 3626 if(address is null) 3627 return WSARecv(file.handle, &buf, 1, null, &flags, overlapped, ocr); 3628 else { 3629 return WSARecvFrom(file.handle, &buf, 1, null, &flags, &(address.address), &(address.addrlen), overlapped, ocr); 3630 } 3631 } 3632 } else { 3633 auto opCall() { 3634 if(address is null) 3635 return core.sys.posix.sys.socket.recv(file.handle, buffer.ptr, buffer.length, flags); 3636 else 3637 return core.sys.posix.sys.socket.recvfrom(file.handle, buffer.ptr, buffer.length, flags, &(address.address), &(address.addrlen)); 3638 } 3639 } 3640 3641 string errorString() { 3642 return "Receive"; 3643 } 3644 } 3645 mixin OverlappedIoRequest!(AsyncReceiveResponse, LowLevelOperation); 3646 3647 this(AsyncSocket socket, ubyte[] buffer, SocketAddress* address, int flags) { 3648 llo = LowLevelOperation(socket, buffer, flags, address); 3649 this.response = typeof(this.response).defaultConstructed; 3650 } 3651 3652 } 3653 /++ 3654 +/ 3655 version(HasSocket) class AsyncReceiveResponse : AsyncOperationResponse { 3656 const ubyte[] bufferWritten; 3657 const SystemErrorCode errorCode; 3658 3659 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 3660 this.errorCode = errorCode; 3661 this.bufferWritten = bufferWritten; 3662 } 3663 3664 override bool wasSuccessful() { 3665 return errorCode.wasSuccessful; 3666 } 3667 } 3668 3669 /++ 3670 +/ 3671 version(HasSocket) class AsyncSendRequest : AsyncOperationRequest { 3672 struct LowLevelOperation { 3673 AsyncSocket file; 3674 const(ubyte)[] buffer; 3675 int flags; 3676 SocketAddress* address; 3677 3678 this(typeof(this.tupleof) args) { 3679 this.tupleof = args; 3680 } 3681 3682 version(Windows) { 3683 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 3684 WSABUF buf; 3685 buf.len = cast(int) buffer.length; 3686 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 3687 3688 if(address is null) 3689 return WSASend(file.handle, &buf, 1, null, flags, overlapped, ocr); 3690 else { 3691 return WSASendTo(file.handle, &buf, 1, null, flags, address.rawAddr, address.rawAddrLength, overlapped, ocr); 3692 } 3693 } 3694 } else { 3695 auto opCall() { 3696 if(address is null) 3697 return core.sys.posix.sys.socket.send(file.handle, buffer.ptr, buffer.length, flags); 3698 else 3699 return core.sys.posix.sys.socket.sendto(file.handle, buffer.ptr, buffer.length, flags, address.rawAddr, address.rawAddrLength); 3700 } 3701 } 3702 3703 string errorString() { 3704 return "Send"; 3705 } 3706 } 3707 mixin OverlappedIoRequest!(AsyncSendResponse, LowLevelOperation); 3708 3709 this(AsyncSocket socket, const(ubyte)[] buffer, SocketAddress* address, int flags) { 3710 llo = LowLevelOperation(socket, buffer, flags, address); 3711 this.response = typeof(this.response).defaultConstructed; 3712 } 3713 } 3714 3715 /++ 3716 +/ 3717 version(HasSocket) class AsyncSendResponse : AsyncOperationResponse { 3718 const ubyte[] bufferWritten; 3719 const SystemErrorCode errorCode; 3720 3721 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 3722 this.errorCode = errorCode; 3723 this.bufferWritten = bufferWritten; 3724 } 3725 3726 override bool wasSuccessful() { 3727 return errorCode.wasSuccessful; 3728 } 3729 3730 } 3731 3732 /++ 3733 A set of sockets bound and ready to accept connections on worker threads. 3734 3735 Depending on the specified address, it can be tcp, tcpv6, unix domain, or all of the above. 3736 3737 NOT IMPLEMENTED / NOT STABLE 3738 +/ 3739 version(HasSocket) class StreamServer { 3740 AsyncSocket[] sockets; 3741 3742 this(SocketAddress[] listenTo, int backlog = 8) { 3743 foreach(listen; listenTo) { 3744 auto socket = new AsyncSocket(listen, SOCK_STREAM); 3745 3746 // FIXME: allInterfaces for ipv6 also covers ipv4 so the bind can fail... 3747 // so we have to permit it to fail w/ address in use if we know we already 3748 // are listening to ipv6 3749 3750 // or there is a setsockopt ipv6 only thing i could set. 3751 3752 socket.bind(listen); 3753 socket.listen(backlog); 3754 sockets ~= socket; 3755 3756 // writeln(socket.localAddress.port); 3757 } 3758 3759 // i have to start accepting on each thread for each socket... 3760 } 3761 // when a new connection arrives, it calls your callback 3762 // can be on a specific thread or on any thread 3763 3764 3765 void start() { 3766 foreach(socket; sockets) { 3767 auto request = socket.accept(); 3768 request.start(); 3769 } 3770 } 3771 } 3772 3773 /+ 3774 unittest { 3775 auto ss = new StreamServer(SocketAddress.localhost(0)); 3776 } 3777 +/ 3778 3779 /++ 3780 A socket bound and ready to use receiveFrom 3781 3782 Depending on the address, it can be udp or unix domain. 3783 3784 NOT IMPLEMENTED / NOT STABLE 3785 +/ 3786 version(HasSocket) class DatagramListener { 3787 // whenever a udp message arrives, it calls your callback 3788 // can be on a specific thread or on any thread 3789 3790 // UDP is realistically just an async read on the bound socket 3791 // just it can get the "from" data out and might need the "more in packet" flag 3792 } 3793 3794 /++ 3795 Just in case I decide to change the implementation some day. 3796 +/ 3797 version(HasFile) alias AsyncAnonymousPipe = AsyncFile; 3798 3799 3800 // AsyncAnonymousPipe connectNamedPipe(AsyncAnonymousPipe preallocated, string name) 3801 3802 // unix fifos are considered just non-seekable files and have no special support in the lib; open them as a regular file w/ the async flag. 3803 3804 // DIRECTORY LISTINGS 3805 // not async, so if you want that, do it in a helper thread 3806 // just a convenient function to have (tho phobos has a decent one too, importing it expensive af) 3807 3808 /++ 3809 Note that the order of items called for your delegate is undefined; if you want it sorted, you'll have to collect and sort yourself. But it *might* be sorted by the OS (on Windows, it almost always is), so consider that when choosing a sorting algorithm. 3810 3811 History: 3812 previously in minigui as a private function. Moved to arsd.core on April 3, 2023 3813 +/ 3814 version(HasFile) GetFilesResult getFiles(string directory, scope void delegate(string name, bool isDirectory) dg) { 3815 // FIXME: my buffers here aren't great lol 3816 3817 SavedArgument[1] argsForException() { 3818 return [ 3819 SavedArgument("directory", LimitedVariant(directory)), 3820 ]; 3821 } 3822 3823 version(Windows) { 3824 WIN32_FIND_DATA data; 3825 // FIXME: if directory ends with / or \\ ? 3826 WCharzBuffer search = WCharzBuffer(directory ~ "/*"); 3827 auto handle = FindFirstFileW(search.ptr, &data); 3828 scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle); 3829 if(handle is INVALID_HANDLE_VALUE) { 3830 if(GetLastError() == ERROR_FILE_NOT_FOUND) 3831 return GetFilesResult.fileNotFound; 3832 throw new WindowsApiException("FindFirstFileW", GetLastError(), argsForException()[]); 3833 } 3834 3835 try_more: 3836 3837 string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]); 3838 3839 dg(name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false); 3840 3841 auto ret = FindNextFileW(handle, &data); 3842 if(ret == 0) { 3843 if(GetLastError() == ERROR_NO_MORE_FILES) 3844 return GetFilesResult.success; 3845 throw new WindowsApiException("FindNextFileW", GetLastError(), argsForException()[]); 3846 } 3847 3848 goto try_more; 3849 3850 } else version(Posix) { 3851 import core.sys.posix.dirent; 3852 import core.stdc.errno; 3853 auto dir = opendir((directory ~ "\0").ptr); 3854 scope(exit) 3855 if(dir) closedir(dir); 3856 if(dir is null) 3857 throw new ErrnoApiException("opendir", errno, argsForException()); 3858 3859 auto dirent = readdir(dir); 3860 if(dirent is null) 3861 return GetFilesResult.fileNotFound; 3862 3863 try_more: 3864 3865 string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup; 3866 3867 dg(name, dirent.d_type == DT_DIR); 3868 3869 dirent = readdir(dir); 3870 if(dirent is null) 3871 return GetFilesResult.success; 3872 3873 goto try_more; 3874 } else static assert(0); 3875 } 3876 3877 /// ditto 3878 enum GetFilesResult { 3879 success, 3880 fileNotFound 3881 } 3882 3883 /++ 3884 This is currently a simplified glob where only the * wildcard in the first or last position gets special treatment or a single * in the middle. 3885 3886 More things may be added later to be more like what Phobos supports. 3887 +/ 3888 bool matchesFilePattern(scope const(char)[] name, scope const(char)[] pattern) { 3889 if(pattern.length == 0) 3890 return false; 3891 if(pattern == "*") 3892 return true; 3893 if(pattern.length > 2 && pattern[0] == '*' && pattern[$-1] == '*') { 3894 // if the rest of pattern appears in name, it is good 3895 return name.indexOf(pattern[1 .. $-1]) != -1; 3896 } else if(pattern[0] == '*') { 3897 // if the rest of pattern is at end of name, it is good 3898 return name.endsWith(pattern[1 .. $]); 3899 } else if(pattern[$-1] == '*') { 3900 // if the rest of pattern is at start of name, it is good 3901 return name.startsWith(pattern[0 .. $-1]); 3902 } else if(pattern.length >= 3) { 3903 auto idx = pattern.indexOf("*"); 3904 if(idx != -1) { 3905 auto lhs = pattern[0 .. idx]; 3906 auto rhs = pattern[idx + 1 .. $]; 3907 if(name.length >= lhs.length + rhs.length) { 3908 return name.startsWith(lhs) && name.endsWith(rhs); 3909 } else { 3910 return false; 3911 } 3912 } 3913 } 3914 3915 return name == pattern; 3916 } 3917 3918 unittest { 3919 assert("test.html".matchesFilePattern("*")); 3920 assert("test.html".matchesFilePattern("*.html")); 3921 assert("test.html".matchesFilePattern("*.*")); 3922 assert("test.html".matchesFilePattern("test.*")); 3923 assert(!"test.html".matchesFilePattern("pest.*")); 3924 assert(!"test.html".matchesFilePattern("*.dhtml")); 3925 3926 assert("test.html".matchesFilePattern("t*.html")); 3927 assert(!"test.html".matchesFilePattern("e*.html")); 3928 } 3929 3930 package(arsd) int indexOf(scope const(char)[] haystack, scope const(char)[] needle) { 3931 if(haystack.length < needle.length) 3932 return -1; 3933 if(haystack == needle) 3934 return 0; 3935 foreach(i; 0 .. haystack.length - needle.length + 1) 3936 if(haystack[i .. i + needle.length] == needle) 3937 return cast(int) i; 3938 return -1; 3939 } 3940 3941 unittest { 3942 assert("foo".indexOf("f") == 0); 3943 assert("foo".indexOf("o") == 1); 3944 assert("foo".indexOf("foo") == 0); 3945 assert("foo".indexOf("oo") == 1); 3946 assert("foo".indexOf("fo") == 0); 3947 assert("foo".indexOf("boo") == -1); 3948 assert("foo".indexOf("food") == -1); 3949 } 3950 3951 package(arsd) bool endsWith(scope const(char)[] haystack, scope const(char)[] needle) { 3952 if(needle.length > haystack.length) 3953 return false; 3954 return haystack[$ - needle.length .. $] == needle; 3955 } 3956 3957 unittest { 3958 assert("foo".endsWith("o")); 3959 assert("foo".endsWith("oo")); 3960 assert("foo".endsWith("foo")); 3961 assert(!"foo".endsWith("food")); 3962 assert(!"foo".endsWith("d")); 3963 } 3964 3965 package(arsd) bool startsWith(scope const(char)[] haystack, scope const(char)[] needle) { 3966 if(needle.length > haystack.length) 3967 return false; 3968 return haystack[0 .. needle.length] == needle; 3969 } 3970 3971 unittest { 3972 assert("foo".startsWith("f")); 3973 assert("foo".startsWith("fo")); 3974 assert("foo".startsWith("foo")); 3975 assert(!"foo".startsWith("food")); 3976 assert(!"foo".startsWith("d")); 3977 } 3978 3979 3980 // FILE/DIR WATCHES 3981 // linux does it by name, windows and bsd do it by handle/descriptor 3982 // dispatches change event to either your thread or maybe the any task` queue. 3983 3984 /++ 3985 PARTIALLY IMPLEMENTED / NOT STABLE 3986 3987 +/ 3988 class DirectoryWatcher { 3989 private { 3990 version(Arsd_core_windows) { 3991 OVERLAPPED overlapped; 3992 HANDLE hDirectory; 3993 ubyte[] buffer; 3994 3995 extern(Windows) 3996 static void overlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) { 3997 typeof(this) rr = cast(typeof(this)) (cast(void*) lpOverlapped - typeof(this).overlapped.offsetof); 3998 3999 // dwErrorCode 4000 auto response = rr.buffer[0 .. dwNumberOfBytesTransferred]; 4001 4002 while(response.length) { 4003 auto fni = cast(FILE_NOTIFY_INFORMATION*) response.ptr; 4004 auto filename = fni.FileName[0 .. fni.FileNameLength]; 4005 4006 if(fni.NextEntryOffset) 4007 response = response[fni.NextEntryOffset .. $]; 4008 else 4009 response = response[$..$]; 4010 4011 // FIXME: I think I need to pin every overlapped op while it is pending 4012 // and unpin it when it is returned. GC.addRoot... but i don't wanna do that 4013 // every op so i guess i should do a refcount scheme similar to the other callback helper. 4014 4015 rr.changeHandler( 4016 FilePath(makeUtf8StringFromWindowsString(filename)), // FIXME: this is a relative path 4017 ChangeOperation.unknown // FIXME this is fni.Action 4018 ); 4019 } 4020 4021 rr.requestRead(); 4022 } 4023 4024 void requestRead() { 4025 DWORD ignored; 4026 if(!ReadDirectoryChangesW( 4027 hDirectory, 4028 buffer.ptr, 4029 cast(int) buffer.length, 4030 recursive, 4031 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, 4032 &ignored, 4033 &overlapped, 4034 &overlappedCompletionRoutine 4035 )) { 4036 auto error = GetLastError(); 4037 /+ 4038 if(error == ERROR_IO_PENDING) { 4039 // not expected here, the docs say it returns true when queued 4040 } 4041 +/ 4042 4043 throw new SystemApiException("ReadDirectoryChangesW", error); 4044 } 4045 } 4046 } else version(Arsd_core_epoll) { 4047 static int inotifyfd = -1; // this is TLS since it is associated with the thread's event loop 4048 static ICoreEventLoop.UnregisterToken inotifyToken; 4049 static CallbackHelper inotifycb; 4050 static DirectoryWatcher[int] watchMappings; 4051 4052 static ~this() { 4053 if(inotifyfd != -1) { 4054 close(inotifyfd); 4055 inotifyfd = -1; 4056 } 4057 } 4058 4059 import core.sys.linux.sys.inotify; 4060 4061 int watchId = -1; 4062 4063 static void inotifyReady() { 4064 // read from it 4065 ubyte[256 /* NAME_MAX + 1 */ + inotify_event.sizeof] sbuffer; 4066 4067 auto ret = read(inotifyfd, sbuffer.ptr, sbuffer.length); 4068 if(ret == -1) { 4069 auto errno = errno; 4070 if(errno == EAGAIN || errno == EWOULDBLOCK) 4071 return; 4072 throw new SystemApiException("read inotify", errno); 4073 } else if(ret == 0) { 4074 assert(0, "I don't think this is ever supposed to happen"); 4075 } 4076 4077 auto buffer = sbuffer[0 .. ret]; 4078 4079 while(buffer.length > 0) { 4080 inotify_event* event = cast(inotify_event*) buffer.ptr; 4081 buffer = buffer[inotify_event.sizeof .. $]; 4082 char[] filename = cast(char[]) buffer[0 .. event.len]; 4083 buffer = buffer[event.len .. $]; 4084 4085 // note that filename is padded with zeroes, so it is actually a stringz 4086 4087 if(auto obj = event.wd in watchMappings) { 4088 (*obj).changeHandler( 4089 FilePath(stringz(filename.ptr).borrow.idup), // FIXME: this is a relative path 4090 ChangeOperation.unknown // FIXME 4091 ); 4092 } else { 4093 // it has probably already been removed 4094 } 4095 } 4096 } 4097 } else version(Arsd_core_kqueue) { 4098 int fd; 4099 CallbackHelper cb; 4100 } 4101 4102 FilePath path; 4103 string globPattern; 4104 bool recursive; 4105 void delegate(FilePath filename, ChangeOperation op) changeHandler; 4106 } 4107 4108 enum ChangeOperation { 4109 unknown, 4110 deleted, // NOTE_DELETE, IN_DELETE, FILE_NOTIFY_CHANGE_FILE_NAME 4111 written, // NOTE_WRITE / NOTE_EXTEND / NOTE_TRUNCATE, IN_MODIFY, FILE_NOTIFY_CHANGE_LAST_WRITE / FILE_NOTIFY_CHANGE_SIZE 4112 renamed, // NOTE_RENAME, the moved from/to in linux, FILE_NOTIFY_CHANGE_FILE_NAME 4113 metadataChanged // NOTE_ATTRIB, IN_ATTRIB, FILE_NOTIFY_CHANGE_ATTRIBUTES 4114 4115 // there is a NOTE_OPEN on freebsd 13, and the access change on Windows. and an open thing on linux. so maybe i can do note open/note_read too. 4116 } 4117 4118 /+ 4119 Windows and Linux work best when you watch directories. The operating system tells you the name of files as they change. 4120 4121 BSD doesn't support this. You can only get names and reports when a file is modified by watching specific files. AS such, when you watch a directory on those systems, your delegate will be called with a null path. Cross-platform applications should check for this and not assume the name is always usable. 4122 4123 inotify is kinda clearly the best of the bunch, with Windows in second place, and kqueue dead last. 4124 4125 4126 If path to watch is a directory, it signals when a file inside the directory (only one layer deep) is created or modified. This is the most efficient on Windows and Linux. 4127 4128 If a path is a file, it only signals when that specific file is written. This is most efficient on BSD. 4129 4130 4131 The delegate is called when something happens. Note that the path modified may not be accurate on all systems when you are watching a directory. 4132 +/ 4133 4134 /++ 4135 Watches a directory and its contents. If the `globPattern` is `null`, it will not attempt to add child items but also will not filter it, meaning you will be left with platform-specific behavior. 4136 4137 On Windows, the globPattern is just used to filter events. 4138 4139 On Linux, the `recursive` flag, if set, will cause it to add additional OS-level watches for each subdirectory. 4140 4141 On BSD, anything other than a null pattern will cause a directory scan to add files to the watch list. 4142 4143 For best results, use the most limited thing you need, as watches can get quite involved on the bsd systems. 4144 4145 Newly added files and subdirectories may not be automatically added in all cases, meaning if it is added and then subsequently modified, you might miss a notification. 4146 4147 If the event queue is too busy, the OS may skip a notification. 4148 4149 You should always offer some way for the user to force a refresh and not rely on notifications being present; they are a convenience when they work, not an always reliable method. 4150 +/ 4151 this(FilePath directoryToWatch, string globPattern, bool recursive, void delegate(FilePath pathModified, ChangeOperation op) dg) { 4152 this.path = directoryToWatch; 4153 this.globPattern = globPattern; 4154 this.recursive = recursive; 4155 this.changeHandler = dg; 4156 4157 version(Arsd_core_windows) { 4158 WCharzBuffer wname = directoryToWatch.path; 4159 buffer = new ubyte[](1024); 4160 hDirectory = CreateFileW( 4161 wname.ptr, 4162 GENERIC_READ, 4163 FILE_SHARE_READ, 4164 null, 4165 OPEN_EXISTING, 4166 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, 4167 null 4168 ); 4169 if(hDirectory == INVALID_HANDLE_VALUE) 4170 throw new SystemApiException("CreateFileW", GetLastError()); 4171 4172 requestRead(); 4173 } else version(Arsd_core_epoll) { 4174 auto el = getThisThreadEventLoop(); 4175 4176 // no need for sync because it is thread-local 4177 if(inotifyfd == -1) { 4178 inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 4179 if(inotifyfd == -1) 4180 throw new SystemApiException("inotify_init1", errno); 4181 4182 inotifycb = new CallbackHelper(&inotifyReady); 4183 inotifyToken = el.addCallbackOnFdReadable(inotifyfd, inotifycb); 4184 } 4185 4186 uint event_mask = IN_CREATE | IN_MODIFY | IN_DELETE; // FIXME 4187 CharzBuffer dtw = directoryToWatch.path; 4188 auto watchId = inotify_add_watch(inotifyfd, dtw.ptr, event_mask); 4189 if(watchId < -1) 4190 throw new SystemApiException("inotify_add_watch", errno, [SavedArgument("path", LimitedVariant(directoryToWatch.path))]); 4191 4192 watchMappings[watchId] = this; 4193 4194 // FIXME: recursive needs to add child things individually 4195 4196 } else version(Arsd_core_kqueue) { 4197 auto el = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 4198 4199 // FIXME: need to scan for globPattern 4200 // when a new file is added, i'll have to diff my list to detect it and open it too 4201 // and recursive might need to scan down too. 4202 4203 kevent_t ev; 4204 4205 import core.sys.posix.fcntl; 4206 CharzBuffer buffer = CharzBuffer(directoryToWatch.path); 4207 fd = ErrnoEnforce!open(buffer.ptr, O_RDONLY); 4208 setCloExec(fd); 4209 4210 cb = new CallbackHelper(&triggered); 4211 4212 EV_SET(&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE, 0, cast(void*) cb); 4213 ErrnoEnforce!kevent(el.kqueuefd, &ev, 1, null, 0, null); 4214 } else assert(0, "Not yet implemented for this platform"); 4215 } 4216 4217 private void triggered() { 4218 writeln("triggered"); 4219 } 4220 4221 void dispose() { 4222 version(Arsd_core_windows) { 4223 CloseHandle(hDirectory); 4224 } else version(Arsd_core_epoll) { 4225 watchMappings.remove(watchId); // I could also do this on the IN_IGNORE notification but idk 4226 inotify_rm_watch(inotifyfd, watchId); 4227 } else version(Arsd_core_kqueue) { 4228 ErrnoEnforce!close(fd); 4229 fd = -1; 4230 } 4231 } 4232 } 4233 4234 version(none) 4235 void main() { 4236 4237 // auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation, AsyncFile.RequirePreexisting.yes); 4238 4239 /+ 4240 getFiles("c:/windows\\", (string filename, bool isDirectory) { 4241 writeln(filename, " ", isDirectory ? "[dir]": "[file]"); 4242 }); 4243 +/ 4244 4245 auto w = new DirectoryWatcher(FilePath("."), "*", false, (path, op) { 4246 writeln(path.path); 4247 }); 4248 getThisThreadEventLoop().run(() => false); 4249 } 4250 4251 /++ 4252 This starts up a local pipe. If it is already claimed, it just communicates with the existing one through the interface. 4253 +/ 4254 class SingleInstanceApplication { 4255 // FIXME 4256 } 4257 4258 version(none) 4259 void main() { 4260 4261 auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation, AsyncFile.RequirePreexisting.yes); 4262 4263 auto buffer = cast(ubyte[]) "hello"; 4264 auto wr = new AsyncWriteRequest(file, buffer, 0); 4265 wr.start(); 4266 4267 wr.waitForCompletion(); 4268 4269 file.close(); 4270 } 4271 4272 /++ 4273 Implementation details of some requests. You shouldn't need to know any of this, the interface is all public. 4274 +/ 4275 mixin template OverlappedIoRequest(Response, LowLevelOperation) { 4276 private { 4277 LowLevelOperation llo; 4278 4279 OwnedClass!Response response; 4280 4281 version(Windows) { 4282 OVERLAPPED overlapped; 4283 4284 extern(Windows) 4285 static void overlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) { 4286 typeof(this) rr = cast(typeof(this)) (cast(void*) lpOverlapped - typeof(this).overlapped.offsetof); 4287 4288 rr.response = typeof(rr.response)(SystemErrorCode(dwErrorCode), rr.llo.buffer[0 .. dwNumberOfBytesTransferred]); 4289 rr.state_ = State.complete; 4290 4291 // FIXME: on complete? 4292 4293 // this will queue our CallbackHelper and that should be run at the end of the event loop after it is woken up by the APC run 4294 } 4295 } 4296 4297 version(Posix) { 4298 ICoreEventLoop.RearmToken eventRegistration; 4299 CallbackHelper cb; 4300 4301 final CallbackHelper getCb() { 4302 if(cb is null) 4303 cb = new CallbackHelper(&cbImpl); 4304 return cb; 4305 } 4306 4307 final void cbImpl() { 4308 // it is ready to complete, time to do it 4309 auto ret = llo(); 4310 markCompleted(ret, errno); 4311 } 4312 4313 void markCompleted(long ret, int errno) { 4314 // maybe i should queue an apc to actually do it, to ensure the event loop has cycled... FIXME 4315 if(ret == -1) 4316 response = typeof(response)(SystemErrorCode(errno), null); 4317 else 4318 response = typeof(response)(SystemErrorCode(0), llo.buffer[0 .. cast(size_t) ret]); 4319 state_ = State.complete; 4320 } 4321 } 4322 } 4323 4324 enum State { 4325 unused, 4326 started, 4327 inProgress, 4328 complete 4329 } 4330 private State state_; 4331 4332 override void start() { 4333 assert(state_ == State.unused); 4334 4335 state_ = State.started; 4336 4337 version(Windows) { 4338 if(llo(&overlapped, &overlappedCompletionRoutine)) { 4339 // all good, though GetLastError() might have some informative info 4340 } else { 4341 // operation failed, the operation is always ReadFileEx or WriteFileEx so it won't give the io pending thing here 4342 // should i issue error async? idk 4343 state_ = State.complete; 4344 throw new SystemApiException(llo.errorString(), GetLastError()); 4345 } 4346 4347 // ReadFileEx always queues, even if it completed synchronously. I *could* check the get overlapped result and sleepex here but i'm prolly better off just letting the event loop do its thing anyway. 4348 } else version(Posix) { 4349 4350 // first try to just do it 4351 auto ret = llo(); 4352 4353 auto errno = errno; 4354 if(ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // unable to complete right now, register and try when it is ready 4355 eventRegistration = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(this.llo.file.handle, this.getCb); 4356 } else { 4357 // i could set errors sync or async and since it couldn't even start, i think a sync exception is the right way 4358 if(ret == -1) 4359 throw new SystemApiException(llo.errorString(), errno); 4360 markCompleted(ret, errno); // it completed synchronously (if it is an error nor not is handled by the completion handler) 4361 } 4362 } 4363 } 4364 4365 4366 override void cancel() { 4367 if(state_ == State.complete) 4368 return; // it has already finished, just leave it alone, no point discarding what is already done 4369 version(Windows) { 4370 if(state_ != State.unused) 4371 Win32Enforce!CancelIoEx(llo.file.AbstractFile.handle, &overlapped); 4372 // Windows will notify us when the cancellation is complete, so we need to wait for that before updating the state 4373 } else version(Posix) { 4374 if(state_ != State.unused) 4375 eventRegistration.unregister(); 4376 markCompleted(-1, ECANCELED); 4377 } 4378 } 4379 4380 override bool isComplete() { 4381 // just always let the event loop do it instead 4382 return state_ == State.complete; 4383 4384 /+ 4385 version(Windows) { 4386 return HasOverlappedIoCompleted(&overlapped); 4387 } else version(Posix) { 4388 return state_ == State.complete; 4389 4390 } 4391 +/ 4392 } 4393 4394 override Response waitForCompletion() { 4395 if(state_ == State.unused) 4396 start(); 4397 4398 // FIXME: if we are inside a fiber, we can set a oncomplete callback and then yield instead... 4399 if(state_ != State.complete) 4400 getThisThreadEventLoop().run(&isComplete); 4401 4402 /+ 4403 version(Windows) { 4404 SleepEx(INFINITE, true); 4405 4406 //DWORD numberTransferred; 4407 //Win32Enforce!GetOverlappedResult(file.handle, &overlapped, &numberTransferred, true); 4408 } else version(Posix) { 4409 getThisThreadEventLoop().run(&isComplete); 4410 } 4411 +/ 4412 4413 return response; 4414 } 4415 } 4416 4417 /++ 4418 You can write to a file asynchronously by creating one of these. 4419 +/ 4420 version(HasSocket) final class AsyncWriteRequest : AsyncOperationRequest { 4421 struct LowLevelOperation { 4422 AsyncFile file; 4423 ubyte[] buffer; 4424 long offset; 4425 4426 this(typeof(this.tupleof) args) { 4427 this.tupleof = args; 4428 } 4429 4430 version(Windows) { 4431 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 4432 overlapped.Offset = (cast(ulong) offset) & 0xffff_ffff; 4433 overlapped.OffsetHigh = ((cast(ulong) offset) >> 32) & 0xffff_ffff; 4434 return WriteFileEx(file.handle, buffer.ptr, cast(int) buffer.length, overlapped, ocr); 4435 } 4436 } else { 4437 auto opCall() { 4438 return core.sys.posix.unistd.write(file.handle, buffer.ptr, buffer.length); 4439 } 4440 } 4441 4442 string errorString() { 4443 return "Write"; 4444 } 4445 } 4446 mixin OverlappedIoRequest!(AsyncWriteResponse, LowLevelOperation); 4447 4448 this(AsyncFile file, ubyte[] buffer, long offset) { 4449 this.llo = LowLevelOperation(file, buffer, offset); 4450 response = typeof(response).defaultConstructed; 4451 } 4452 } 4453 4454 /++ 4455 4456 +/ 4457 class AsyncWriteResponse : AsyncOperationResponse { 4458 const ubyte[] bufferWritten; 4459 const SystemErrorCode errorCode; 4460 4461 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 4462 this.errorCode = errorCode; 4463 this.bufferWritten = bufferWritten; 4464 } 4465 4466 override bool wasSuccessful() { 4467 return errorCode.wasSuccessful; 4468 } 4469 } 4470 4471 /++ 4472 4473 +/ 4474 version(HasSocket) final class AsyncReadRequest : AsyncOperationRequest { 4475 struct LowLevelOperation { 4476 AsyncFile file; 4477 ubyte[] buffer; 4478 long offset; 4479 4480 this(typeof(this.tupleof) args) { 4481 this.tupleof = args; 4482 } 4483 4484 version(Windows) { 4485 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 4486 overlapped.Offset = (cast(ulong) offset) & 0xffff_ffff; 4487 overlapped.OffsetHigh = ((cast(ulong) offset) >> 32) & 0xffff_ffff; 4488 return ReadFileEx(file.handle, buffer.ptr, cast(int) buffer.length, overlapped, ocr); 4489 } 4490 } else { 4491 auto opCall() { 4492 return core.sys.posix.unistd.read(file.handle, buffer.ptr, buffer.length); 4493 } 4494 } 4495 4496 string errorString() { 4497 return "Read"; 4498 } 4499 } 4500 mixin OverlappedIoRequest!(AsyncReadResponse, LowLevelOperation); 4501 4502 /++ 4503 The file must have the overlapped flag enabled on Windows and the nonblock flag set on Posix. 4504 4505 The buffer MUST NOT be touched by you - not used by another request, modified, read, or freed, including letting a static array going out of scope - until this request's `isComplete` returns `true`. 4506 4507 The offset is where to start reading a disk file. For all other types of files, pass 0. 4508 +/ 4509 this(AsyncFile file, ubyte[] buffer, long offset) { 4510 this.llo = LowLevelOperation(file, buffer, offset); 4511 response = typeof(response).defaultConstructed; 4512 } 4513 4514 /++ 4515 4516 +/ 4517 // abstract void repeat(); 4518 } 4519 4520 /++ 4521 4522 +/ 4523 class AsyncReadResponse : AsyncOperationResponse { 4524 const ubyte[] bufferRead; 4525 const SystemErrorCode errorCode; 4526 4527 this(SystemErrorCode errorCode, const(ubyte)[] bufferRead) { 4528 this.errorCode = errorCode; 4529 this.bufferRead = bufferRead; 4530 } 4531 4532 override bool wasSuccessful() { 4533 return errorCode.wasSuccessful; 4534 } 4535 } 4536 4537 /+ 4538 Tasks: 4539 startTask() 4540 startSubTask() - what if it just did this when it knows it is being run from inside a task? 4541 runHelperFunction() - whomever it reports to is the parent 4542 +/ 4543 4544 version(HasThread) class ScheduableTask : Fiber { 4545 private void delegate() dg; 4546 4547 // linked list stuff 4548 private static ScheduableTask taskRoot; 4549 private ScheduableTask previous; 4550 private ScheduableTask next; 4551 4552 // need the controlling thread to know how to wake it up if it receives a message 4553 private Thread controllingThread; 4554 4555 // the api 4556 4557 this(void delegate() dg) { 4558 assert(dg !is null); 4559 4560 this.dg = dg; 4561 super(&taskRunner); 4562 4563 if(taskRoot !is null) { 4564 this.next = taskRoot; 4565 taskRoot.previous = this; 4566 } 4567 taskRoot = this; 4568 } 4569 4570 /+ 4571 enum BehaviorOnCtrlC { 4572 ignore, 4573 cancel, 4574 deliverMessage 4575 } 4576 +/ 4577 4578 private bool cancelled; 4579 4580 public void cancel() { 4581 this.cancelled = true; 4582 // if this is running, we can throw immediately 4583 // otherwise if we're calling from an appropriate thread, we can call it immediately 4584 // otherwise we need to queue a wakeup to its own thread. 4585 // tbh we should prolly just queue it every time 4586 } 4587 4588 private void taskRunner() { 4589 try { 4590 dg(); 4591 } catch(TaskCancelledException tce) { 4592 // this space intentionally left blank; 4593 // the purpose of this exception is to just 4594 // let the fiber's destructors run before we 4595 // let it die. 4596 } catch(Throwable t) { 4597 if(taskUncaughtException is null) { 4598 throw t; 4599 } else { 4600 taskUncaughtException(t); 4601 } 4602 } finally { 4603 if(this is taskRoot) { 4604 taskRoot = taskRoot.next; 4605 if(taskRoot !is null) 4606 taskRoot.previous = null; 4607 } else { 4608 assert(this.previous !is null); 4609 assert(this.previous.next is this); 4610 this.previous.next = this.next; 4611 if(this.next !is null) 4612 this.next.previous = this.previous; 4613 } 4614 } 4615 } 4616 } 4617 4618 /++ 4619 4620 +/ 4621 void delegate(Throwable t) taskUncaughtException; 4622 4623 /++ 4624 Gets an object that lets you control a schedulable task (which is a specialization of a fiber) and can be used in an `if` statement. 4625 4626 --- 4627 if(auto controller = inSchedulableTask()) { 4628 controller.yieldUntilReadable(...); 4629 } 4630 --- 4631 4632 History: 4633 Added August 11, 2023 (dub v11.1) 4634 +/ 4635 version(HasThread) SchedulableTaskController inSchedulableTask() { 4636 import core.thread.fiber; 4637 4638 if(auto fiber = Fiber.getThis) { 4639 return SchedulableTaskController(cast(ScheduableTask) fiber); 4640 } 4641 4642 return SchedulableTaskController(null); 4643 } 4644 4645 /// ditto 4646 version(HasThread) struct SchedulableTaskController { 4647 private this(ScheduableTask fiber) { 4648 this.fiber = fiber; 4649 } 4650 4651 private ScheduableTask fiber; 4652 4653 /++ 4654 4655 +/ 4656 bool opCast(T : bool)() { 4657 return fiber !is null; 4658 } 4659 4660 /++ 4661 4662 +/ 4663 version(Posix) 4664 void yieldUntilReadable(NativeFileHandle handle) { 4665 assert(fiber !is null); 4666 4667 auto cb = new CallbackHelper(() { fiber.call(); }); 4668 4669 // FIXME: if the fd is already registered in this thread it can throw... 4670 version(Windows) 4671 auto rearmToken = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(handle, cb); 4672 else 4673 auto rearmToken = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(handle, cb); 4674 4675 // FIXME: this is only valid if the fiber is only ever going to run in this thread! 4676 fiber.yield(); 4677 4678 rearmToken.unregister(); 4679 4680 // what if there are other messages, like a ctrl+c? 4681 if(fiber.cancelled) 4682 throw new TaskCancelledException(); 4683 } 4684 4685 version(Windows) 4686 void yieldUntilSignaled(NativeFileHandle handle) { 4687 // add it to the WaitForMultipleObjects thing w/ a cb 4688 } 4689 } 4690 4691 class TaskCancelledException : object.Exception { 4692 this() { 4693 super("Task cancelled"); 4694 } 4695 } 4696 4697 version(HasThread) private class CoreWorkerThread : Thread { 4698 this(EventLoopType type) { 4699 this.type = type; 4700 4701 // task runners are supposed to have smallish stacks since they either just run a single callback or call into fibers 4702 // the helper runners might be a bit bigger tho 4703 super(&run); 4704 } 4705 void run() { 4706 eventLoop = getThisThreadEventLoop(this.type); 4707 atomicOp!"+="(startedCount, 1); 4708 atomicOp!"+="(runningCount, 1); 4709 scope(exit) { 4710 atomicOp!"-="(runningCount, 1); 4711 } 4712 4713 eventLoop.run(() => cancelled); 4714 } 4715 4716 private bool cancelled; 4717 4718 void cancel() { 4719 cancelled = true; 4720 } 4721 4722 EventLoopType type; 4723 ICoreEventLoop eventLoop; 4724 4725 __gshared static { 4726 CoreWorkerThread[] taskRunners; 4727 CoreWorkerThread[] helperRunners; 4728 ICoreEventLoop mainThreadLoop; 4729 4730 // for the helper function thing on the bsds i could have my own little circular buffer of availability 4731 4732 shared(int) startedCount; 4733 shared(int) runningCount; 4734 4735 bool started; 4736 4737 void setup(int numberOfTaskRunners, int numberOfHelpers) { 4738 assert(!started); 4739 synchronized { 4740 mainThreadLoop = getThisThreadEventLoop(); 4741 4742 foreach(i; 0 .. numberOfTaskRunners) { 4743 auto nt = new CoreWorkerThread(EventLoopType.TaskRunner); 4744 taskRunners ~= nt; 4745 nt.start(); 4746 } 4747 foreach(i; 0 .. numberOfHelpers) { 4748 auto nt = new CoreWorkerThread(EventLoopType.HelperWorker); 4749 helperRunners ~= nt; 4750 nt.start(); 4751 } 4752 4753 const expectedCount = numberOfHelpers + numberOfTaskRunners; 4754 4755 while(startedCount < expectedCount) { 4756 Thread.yield(); 4757 } 4758 4759 started = true; 4760 } 4761 } 4762 4763 void cancelAll() { 4764 foreach(runner; taskRunners) 4765 runner.cancel(); 4766 foreach(runner; helperRunners) 4767 runner.cancel(); 4768 4769 } 4770 } 4771 } 4772 4773 private int numberOfCpus() { 4774 return 4; // FIXME 4775 } 4776 4777 /++ 4778 To opt in to the full functionality of this module with customization opportunity, create one and only one of these objects that is valid for exactly the lifetime of the application. 4779 4780 Normally, this means writing a main like this: 4781 4782 --- 4783 import arsd.core; 4784 void main() { 4785 ArsdCoreApplication app = ArsdCoreApplication("Your app name"); 4786 4787 // do your setup here 4788 4789 // the rest of your code here 4790 } 4791 --- 4792 4793 Its destructor runs the event loop then waits to for the workers to finish to clean them up. 4794 +/ 4795 // FIXME: single instance? 4796 version(HasThread) struct ArsdCoreApplication { 4797 private ICoreEventLoop impl; 4798 4799 /++ 4800 default number of threads is to split your cpus between blocking function runners and task runners 4801 +/ 4802 this(string applicationName) { 4803 auto num = numberOfCpus(); 4804 num /= 2; 4805 if(num <= 0) 4806 num = 1; 4807 this(applicationName, num, num); 4808 } 4809 4810 /++ 4811 4812 +/ 4813 this(string applicationName, int numberOfTaskRunners, int numberOfHelpers) { 4814 impl = getThisThreadEventLoop(EventLoopType.Explicit); 4815 CoreWorkerThread.setup(numberOfTaskRunners, numberOfHelpers); 4816 } 4817 4818 @disable this(); 4819 @disable this(this); 4820 /++ 4821 This must be deterministically destroyed. 4822 +/ 4823 @disable new(); 4824 4825 ~this() { 4826 if(!alreadyRun) 4827 run(); 4828 exitApplication(); 4829 waitForWorkersToExit(3000); 4830 } 4831 4832 void exitApplication() { 4833 CoreWorkerThread.cancelAll(); 4834 } 4835 4836 void waitForWorkersToExit(int timeoutMilliseconds) { 4837 4838 } 4839 4840 private bool alreadyRun; 4841 4842 void run() { 4843 impl.run(() => false); 4844 alreadyRun = true; 4845 } 4846 } 4847 4848 4849 private class CoreEventLoopImplementation : ICoreEventLoop { 4850 version(EmptyEventLoop) RunOnceResult runOnce(Duration timeout = Duration.max) { return RunOnceResult(RunOnceResult.Possibilities.LocalExit); } 4851 version(EmptyCoreEvent) 4852 { 4853 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb){return typeof(return).init;} 4854 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb){return typeof(return).init;} 4855 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb){return typeof(return).init;} 4856 private void rearmFd(RearmToken token) {} 4857 } 4858 4859 4860 private { 4861 static struct LoopIterationDelegate { 4862 void delegate() dg; 4863 uint flags; 4864 } 4865 LoopIterationDelegate[] loopIterationDelegates; 4866 4867 void runLoopIterationDelegates() { 4868 foreach(lid; loopIterationDelegates) 4869 lid.dg(); 4870 } 4871 } 4872 4873 void addDelegateOnLoopIteration(void delegate() dg, uint timingFlags) { 4874 loopIterationDelegates ~= LoopIterationDelegate(dg, timingFlags); 4875 } 4876 4877 version(Arsd_core_kqueue) { 4878 // this thread apc dispatches go as a custom event to the queue 4879 // the other queues go through one byte at a time pipes (barf). freebsd 13 and newest nbsd have eventfd too tho so maybe i can use them but the other kqueue systems don't. 4880 4881 RunOnceResult runOnce(Duration timeout = Duration.max) { 4882 kevent_t[16] ev; 4883 //timespec tout = timespec(1, 0); 4884 auto nev = kevent(kqueuefd, null, 0, ev.ptr, ev.length, null/*&tout*/); 4885 if(nev == -1) { 4886 // FIXME: EINTR 4887 throw new SystemApiException("kevent", errno); 4888 } else if(nev == 0) { 4889 // timeout 4890 } else { 4891 foreach(event; ev[0 .. nev]) { 4892 if(event.filter == EVFILT_SIGNAL) { 4893 // FIXME: I could prolly do this better tbh 4894 markSignalOccurred(cast(int) event.ident); 4895 signalChecker(); 4896 } else { 4897 // FIXME: event.filter more specific? 4898 CallbackHelper cb = cast(CallbackHelper) event.udata; 4899 cb.call(); 4900 } 4901 } 4902 } 4903 4904 runLoopIterationDelegates(); 4905 4906 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 4907 } 4908 4909 // FIXME: idk how to make one event that multiple kqueues can listen to w/o being shared 4910 // maybe a shared kqueue could work that the thread kqueue listen to (which i rejected for 4911 // epoll cuz it caused thundering herd problems but maybe it'd work here) 4912 4913 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 4914 kevent_t ev; 4915 4916 EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 4917 4918 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4919 4920 return UnregisterToken(this, fd, cb); 4921 } 4922 4923 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 4924 kevent_t ev; 4925 4926 EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 4927 4928 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4929 4930 return RearmToken(true, this, fd, cb, 0); 4931 } 4932 4933 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 4934 kevent_t ev; 4935 4936 EV_SET(&ev, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 4937 4938 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4939 4940 return RearmToken(false, this, fd, cb, 0); 4941 } 4942 4943 private void rearmFd(RearmToken token) { 4944 if(token.readable) 4945 cast(void) addCallbackOnFdReadableOneShot(token.fd, token.cb); 4946 else 4947 cast(void) addCallbackOnFdWritableOneShot(token.fd, token.cb); 4948 } 4949 4950 private void triggerGlobalEvent() { 4951 ubyte a; 4952 import core.sys.posix.unistd; 4953 write(kqueueGlobalFd[1], &a, 1); 4954 } 4955 4956 private this() { 4957 kqueuefd = ErrnoEnforce!kqueue(); 4958 setCloExec(kqueuefd); // FIXME O_CLOEXEC 4959 4960 if(kqueueGlobalFd[0] == 0) { 4961 import core.sys.posix.unistd; 4962 pipe(kqueueGlobalFd); 4963 setCloExec(kqueueGlobalFd[0]); 4964 setCloExec(kqueueGlobalFd[1]); 4965 4966 signal(SIGINT, SIG_IGN); // FIXME 4967 } 4968 4969 kevent_t ev; 4970 4971 EV_SET(&ev, SIGCHLD, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, null); 4972 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4973 EV_SET(&ev, SIGINT, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, null); 4974 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4975 4976 globalEventSent = new CallbackHelper(&readGlobalEvent); 4977 EV_SET(&ev, kqueueGlobalFd[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, cast(void*) globalEventSent); 4978 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 4979 } 4980 4981 private int kqueuefd = -1; 4982 4983 private CallbackHelper globalEventSent; 4984 void readGlobalEvent() { 4985 kevent_t event; 4986 4987 import core.sys.posix.unistd; 4988 ubyte a; 4989 read(kqueueGlobalFd[0], &a, 1); 4990 4991 // FIXME: the thread is woken up, now we need to check the circualr buffer queue 4992 } 4993 4994 private __gshared int[2] kqueueGlobalFd; 4995 } 4996 4997 /+ 4998 // this setup needs no extra allocation 4999 auto op = read(file, buffer); 5000 op.oncomplete = &thisfiber.call; 5001 op.start(); 5002 thisfiber.yield(); 5003 auto result = op.waitForCompletion(); // guaranteed to return instantly thanks to previous setup 5004 5005 can generically abstract that into: 5006 5007 auto result = thisTask.await(read(file, buffer)); 5008 5009 5010 You MUST NOT use buffer in any way - not read, modify, deallocate, reuse, anything - until the PendingOperation is complete. 5011 5012 Note that PendingOperation may just be a wrapper around an internally allocated object reference... but then if you do a waitForFirstToComplete what happens? 5013 5014 those could of course just take the value type things 5015 +/ 5016 5017 5018 version(Arsd_core_windows) { 5019 // all event loops share the one iocp, Windows 5020 // manages how to do it 5021 __gshared HANDLE iocpTaskRunners; 5022 __gshared HANDLE iocpWorkers; 5023 5024 HANDLE[] handles; 5025 CallbackHelper[] handlesCbs; 5026 5027 void unregisterHandle(HANDLE handle, CallbackHelper cb) { 5028 foreach(idx, h; handles) 5029 if(h is handle && handlesCbs[idx] is cb) { 5030 handles[idx] = handles[$-1]; 5031 handles = handles[0 .. $-1].assumeSafeAppend; 5032 5033 handlesCbs[idx] = handlesCbs[$-1]; 5034 handlesCbs = handlesCbs[0 .. $-1].assumeSafeAppend; 5035 } 5036 } 5037 5038 UnregisterToken addCallbackOnHandleReady(HANDLE handle, CallbackHelper cb) { 5039 handles ~= handle; 5040 handlesCbs ~= cb; 5041 5042 return UnregisterToken(this, handle, cb); 5043 } 5044 5045 // i think to terminate i just have to post the message at least once for every thread i know about, maybe a few more times for threads i don't know about. 5046 5047 bool isWorker; // if it is a worker we wait on the iocp, if not we wait on msg 5048 5049 RunOnceResult runOnce(Duration timeout = Duration.max) { 5050 if(isWorker) { 5051 // this function is only supported on Windows Vista and up, so using this 5052 // means dropping support for XP. 5053 //GetQueuedCompletionStatusEx(); 5054 assert(0); // FIXME 5055 } else { 5056 auto wto = 0; 5057 5058 auto waitResult = MsgWaitForMultipleObjectsEx( 5059 cast(int) handles.length, handles.ptr, 5060 (wto == 0 ? INFINITE : wto), /* timeout */ 5061 0x04FF, /* QS_ALLINPUT */ 5062 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 5063 5064 enum WAIT_OBJECT_0 = 0; 5065 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 5066 auto h = handles[waitResult - WAIT_OBJECT_0]; 5067 auto cb = handlesCbs[waitResult - WAIT_OBJECT_0]; 5068 cb.call(); 5069 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 5070 // message ready 5071 int count; 5072 MSG message; 5073 while(PeekMessage(&message, null, 0, 0, PM_NOREMOVE)) { // need to peek since sometimes MsgWaitForMultipleObjectsEx returns even though GetMessage can block. tbh i don't fully understand it but the docs say it is foreground activation 5074 auto ret = GetMessage(&message, null, 0, 0); 5075 if(ret == -1) 5076 throw new WindowsApiException("GetMessage", GetLastError()); 5077 TranslateMessage(&message); 5078 DispatchMessage(&message); 5079 5080 count++; 5081 if(count > 10) 5082 break; // take the opportunity to catch up on other events 5083 5084 if(ret == 0) { // WM_QUIT 5085 exitApplication(); 5086 } 5087 } 5088 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 5089 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 5090 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 5091 // timeout, should never happen since we aren't using it 5092 } else if(waitResult == 0xFFFFFFFF) { 5093 // failed 5094 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 5095 } else { 5096 // idk.... 5097 } 5098 } 5099 5100 runLoopIterationDelegates(); 5101 5102 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 5103 } 5104 } 5105 5106 version(Posix) { 5107 private __gshared uint sigChildHappened = 0; 5108 private __gshared uint sigIntrHappened = 0; 5109 5110 static void signalChecker() { 5111 if(cas(&sigChildHappened, 1, 0)) { 5112 while(true) { // multiple children could have exited before we processed the notification 5113 5114 import core.sys.posix.sys.wait; 5115 5116 int status; 5117 auto pid = waitpid(-1, &status, WNOHANG); 5118 if(pid == -1) { 5119 import core.stdc.errno; 5120 auto errno = errno; 5121 if(errno == ECHILD) 5122 break; // also all done, there are no children left 5123 // no need to check EINTR since we set WNOHANG 5124 throw new ErrnoApiException("waitpid", errno); 5125 } 5126 if(pid == 0) 5127 break; // all done, all children are still running 5128 5129 // look up the pid for one of our objects 5130 // if it is found, inform it of its status 5131 // and then inform its controlling thread 5132 // to wake up so it can check its waitForCompletion, 5133 // trigger its callbacks, etc. 5134 5135 ExternalProcess.recordChildTerminated(pid, status); 5136 } 5137 5138 } 5139 if(cas(&sigIntrHappened, 1, 0)) { 5140 // FIXME 5141 import core.stdc.stdlib; 5142 exit(0); 5143 } 5144 } 5145 5146 /++ 5147 Informs the arsd.core system that the given signal happened. You can call this from inside a signal handler. 5148 +/ 5149 public static void markSignalOccurred(int sigNumber) nothrow { 5150 import core.sys.posix.unistd; 5151 5152 if(sigNumber == SIGCHLD) 5153 volatileStore(&sigChildHappened, 1); 5154 if(sigNumber == SIGINT) 5155 volatileStore(&sigIntrHappened, 1); 5156 5157 version(Arsd_core_epoll) { 5158 ulong writeValue = 1; 5159 write(signalPipeFd, &writeValue, writeValue.sizeof); 5160 } 5161 } 5162 } 5163 5164 version(Arsd_core_epoll) { 5165 5166 import core.sys.linux.epoll; 5167 import core.sys.linux.sys.eventfd; 5168 5169 private this() { 5170 5171 if(!globalsInitialized) { 5172 synchronized { 5173 if(!globalsInitialized) { 5174 // blocking signals is problematic because it is inherited by child processes 5175 // and that can be problematic for general purpose stuff so i use a self pipe 5176 // here. though since it is linux, im using an eventfd instead just to notify 5177 signalPipeFd = ErrnoEnforce!eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); 5178 signalReaderCallback = new CallbackHelper(&signalReader); 5179 5180 runInTaskRunnerQueue = new CallbackQueue("task runners", true); 5181 runInHelperThreadQueue = new CallbackQueue("helper threads", true); 5182 5183 setSignalHandlers(); 5184 5185 globalsInitialized = true; 5186 } 5187 } 5188 } 5189 5190 epollfd = epoll_create1(EPOLL_CLOEXEC); 5191 5192 // FIXME: ensure UI events get top priority 5193 5194 // global listeners 5195 5196 // FIXME: i should prolly keep the tokens and release them when tearing down. 5197 5198 cast(void) addCallbackOnFdReadable(signalPipeFd, signalReaderCallback); 5199 if(true) { // FIXME: if this is a task runner vs helper thread vs ui thread 5200 cast(void) addCallbackOnFdReadable(runInTaskRunnerQueue.fd, runInTaskRunnerQueue.callback); 5201 runInTaskRunnerQueue.callback.addref(); 5202 } else { 5203 cast(void) addCallbackOnFdReadable(runInHelperThreadQueue.fd, runInHelperThreadQueue.callback); 5204 runInHelperThreadQueue.callback.addref(); 5205 } 5206 5207 // local listener 5208 thisThreadQueue = new CallbackQueue("this thread", false); 5209 cast(void) addCallbackOnFdReadable(thisThreadQueue.fd, thisThreadQueue.callback); 5210 5211 // what are we going to do about timers? 5212 } 5213 5214 void teardown() { 5215 import core.sys.posix.fcntl; 5216 import core.sys.posix.unistd; 5217 5218 close(epollfd); 5219 epollfd = -1; 5220 5221 thisThreadQueue.teardown(); 5222 5223 // FIXME: should prolly free anything left in the callback queue, tho those could also be GC managed tbh. 5224 } 5225 5226 /+ // i call it explicitly at the thread exit instead, but worker threads aren't really supposed to exit generally speaking till process done anyway 5227 static ~this() { 5228 teardown(); 5229 } 5230 +/ 5231 5232 static void teardownGlobals() { 5233 import core.sys.posix.fcntl; 5234 import core.sys.posix.unistd; 5235 5236 synchronized { 5237 restoreSignalHandlers(); 5238 close(signalPipeFd); 5239 signalReaderCallback.release(); 5240 5241 runInTaskRunnerQueue.teardown(); 5242 runInHelperThreadQueue.teardown(); 5243 5244 globalsInitialized = false; 5245 } 5246 5247 } 5248 5249 5250 private static final class CallbackQueue { 5251 int fd = -1; 5252 string name; 5253 CallbackHelper callback; 5254 SynchronizedCircularBuffer!CallbackHelper queue; 5255 5256 this(string name, bool dequeueIsShared) { 5257 this.name = name; 5258 queue = typeof(queue)(this); 5259 5260 fd = ErrnoEnforce!eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | (dequeueIsShared ? EFD_SEMAPHORE : 0)); 5261 5262 callback = new CallbackHelper(dequeueIsShared ? &sharedDequeueCb : &threadLocalDequeueCb); 5263 } 5264 5265 bool resetEvent() { 5266 import core.sys.posix.unistd; 5267 ulong count; 5268 return read(fd, &count, count.sizeof) == count.sizeof; 5269 } 5270 5271 void sharedDequeueCb() { 5272 if(resetEvent()) { 5273 auto cb = queue.dequeue(); 5274 cb.call(); 5275 cb.release(); 5276 } 5277 } 5278 5279 void threadLocalDequeueCb() { 5280 CallbackHelper[16] buffer; 5281 foreach(cb; queue.dequeueSeveral(buffer[], () { resetEvent(); })) { 5282 cb.call(); 5283 cb.release(); 5284 } 5285 } 5286 5287 void enqueue(CallbackHelper cb) { 5288 if(queue.enqueue(cb)) { 5289 import core.sys.posix.unistd; 5290 ulong count = 1; 5291 ErrnoEnforce!write(fd, &count, count.sizeof); 5292 } else { 5293 throw new ArsdException!"queue is full"(name); 5294 } 5295 } 5296 5297 void teardown() { 5298 import core.sys.posix.fcntl; 5299 import core.sys.posix.unistd; 5300 5301 close(fd); 5302 fd = -1; 5303 5304 callback.release(); 5305 } 5306 } 5307 5308 // there's a global instance of this we refer back to 5309 private __gshared { 5310 bool globalsInitialized; 5311 5312 CallbackHelper signalReaderCallback; 5313 5314 CallbackQueue runInTaskRunnerQueue; 5315 CallbackQueue runInHelperThreadQueue; 5316 5317 int exitEventFd = -1; // FIXME: implement 5318 } 5319 5320 // and then the local loop 5321 private { 5322 int epollfd = -1; 5323 5324 CallbackQueue thisThreadQueue; 5325 } 5326 5327 // signal stuff { 5328 import core.sys.posix.signal; 5329 5330 private __gshared sigaction_t oldSigIntr; 5331 private __gshared sigaction_t oldSigChld; 5332 private __gshared sigaction_t oldSigPipe; 5333 5334 private __gshared int signalPipeFd = -1; 5335 // sigpipe not important, i handle errors on the writes 5336 5337 public static void setSignalHandlers() { 5338 static extern(C) void interruptHandler(int sigNumber) nothrow { 5339 markSignalOccurred(sigNumber); 5340 5341 /+ 5342 // calling the old handler is non-trivial since there can be ignore 5343 // or default or a plain handler or a sigaction 3 arg handler and i 5344 // i don't think it is worth teh complication 5345 sigaction_t* oldHandler; 5346 if(sigNumber == SIGCHLD) 5347 oldHandler = &oldSigChld; 5348 else if(sigNumber == SIGINT) 5349 oldHandler = &oldSigIntr; 5350 if(oldHandler && oldHandler.sa_handler) 5351 oldHandler 5352 +/ 5353 } 5354 5355 sigaction_t n; 5356 n.sa_handler = &interruptHandler; 5357 n.sa_mask = cast(sigset_t) 0; 5358 n.sa_flags = 0; 5359 sigaction(SIGINT, &n, &oldSigIntr); 5360 sigaction(SIGCHLD, &n, &oldSigChld); 5361 5362 n.sa_handler = SIG_IGN; 5363 sigaction(SIGPIPE, &n, &oldSigPipe); 5364 } 5365 5366 public static void restoreSignalHandlers() { 5367 sigaction(SIGINT, &oldSigIntr, null); 5368 sigaction(SIGCHLD, &oldSigChld, null); 5369 sigaction(SIGPIPE, &oldSigPipe, null); 5370 } 5371 5372 private static void signalReader() { 5373 import core.sys.posix.unistd; 5374 ulong number; 5375 read(signalPipeFd, &number, number.sizeof); 5376 5377 signalChecker(); 5378 } 5379 // signal stuff done } 5380 5381 // the any thread poll is just registered in the this thread poll w/ exclusive. nobody actaully epoll_waits 5382 // on the global one directly. 5383 5384 RunOnceResult runOnce(Duration timeout = Duration.max) { 5385 epoll_event[16] events; 5386 auto ret = epoll_wait(epollfd, events.ptr, cast(int) events.length, -1); // FIXME: timeout 5387 if(ret == -1) { 5388 import core.stdc.errno; 5389 if(errno == EINTR) { 5390 return RunOnceResult(RunOnceResult.Possibilities.Interrupted); 5391 } 5392 throw new ErrnoApiException("epoll_wait", errno); 5393 } else if(ret == 0) { 5394 // timeout 5395 } else { 5396 // loop events and call associated callbacks 5397 foreach(event; events[0 .. ret]) { 5398 auto flags = event.events; 5399 auto cbObject = cast(CallbackHelper) event.data.ptr; 5400 5401 // FIXME: or if it is an error... 5402 // EPOLLERR - write end of pipe when read end closed or other error. and EPOLLHUP - terminal hangup or read end when write end close (but it will give 0 reading after that soon anyway) 5403 5404 cbObject.call(); 5405 } 5406 } 5407 5408 runLoopIterationDelegates(); 5409 5410 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 5411 } 5412 5413 // building blocks for low-level integration with the loop 5414 5415 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 5416 epoll_event event; 5417 event.data.ptr = cast(void*) cb; 5418 event.events = EPOLLIN | EPOLLEXCLUSIVE; 5419 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 5420 throw new ErrnoApiException("epoll_ctl", errno); 5421 5422 return UnregisterToken(this, fd, cb); 5423 } 5424 5425 /++ 5426 Adds a one-off callback that you can optionally rearm when it happens. 5427 +/ 5428 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 5429 epoll_event event; 5430 event.data.ptr = cast(void*) cb; 5431 event.events = EPOLLIN | EPOLLONESHOT; 5432 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 5433 throw new ErrnoApiException("epoll_ctl", errno); 5434 5435 return RearmToken(true, this, fd, cb, EPOLLIN | EPOLLONESHOT); 5436 } 5437 5438 /++ 5439 Adds a one-off callback that you can optionally rearm when it happens. 5440 +/ 5441 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 5442 epoll_event event; 5443 event.data.ptr = cast(void*) cb; 5444 event.events = EPOLLOUT | EPOLLONESHOT; 5445 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 5446 throw new ErrnoApiException("epoll_ctl", errno); 5447 5448 return RearmToken(false, this, fd, cb, EPOLLOUT | EPOLLONESHOT); 5449 } 5450 5451 private void unregisterFd(int fd) { 5452 epoll_event event; 5453 if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event) == -1) 5454 throw new ErrnoApiException("epoll_ctl", errno); 5455 } 5456 5457 private void rearmFd(RearmToken token) { 5458 epoll_event event; 5459 event.data.ptr = cast(void*) token.cb; 5460 event.events = token.flags; 5461 if(epoll_ctl(epollfd, EPOLL_CTL_MOD, token.fd, &event) == -1) 5462 throw new ErrnoApiException("epoll_ctl", errno); 5463 } 5464 5465 // Disk files will have to be sent as messages to a worker to do the read and report back a completion packet. 5466 } 5467 5468 version(Arsd_core_kqueue) { 5469 // FIXME 5470 } 5471 5472 // cross platform adapters 5473 void setTimeout() {} 5474 void addFileOrDirectoryChangeListener(FilePath name, uint flags, bool recursive = false) {} 5475 } 5476 5477 // deduplication???????// 5478 bool postMessage(ThreadToRunIn destination, void delegate() code) { 5479 return false; 5480 } 5481 bool postMessage(ThreadToRunIn destination, Object message) { 5482 return false; 5483 } 5484 5485 /+ 5486 void main() { 5487 // FIXME: the offset doesn't seem to be done right 5488 auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation); 5489 file.write("hello", 10).waitForCompletion(); 5490 } 5491 +/ 5492 5493 // to test the mailboxes 5494 /+ 5495 void main() { 5496 /+ 5497 import std.stdio; 5498 Thread[4] pool; 5499 5500 bool shouldExit; 5501 5502 static int received; 5503 5504 static void tester() { 5505 received++; 5506 //writeln(cast(void*) Thread.getThis, " ", received); 5507 } 5508 5509 foreach(ref thread; pool) { 5510 thread = new Thread(() { 5511 getThisThreadEventLoop().run(() { 5512 return shouldExit; 5513 }); 5514 }); 5515 thread.start(); 5516 } 5517 5518 getThisThreadEventLoop(); // ensure it is all initialized before proceeding. FIXME: i should have an ensure initialized function i do on most the public apis. 5519 5520 int lol; 5521 5522 try 5523 foreach(i; 0 .. 6000) { 5524 CoreEventLoopImplementation.runInTaskRunnerQueue.enqueue(new CallbackHelper(&tester)); 5525 lol = cast(int) i; 5526 } 5527 catch(ArsdExceptionBase e) { 5528 Thread.sleep(50.msecs); 5529 writeln(e); 5530 writeln(lol); 5531 } 5532 5533 import core.stdc.stdlib; 5534 exit(0); 5535 5536 version(none) 5537 foreach(i; 0 .. 100) 5538 CoreEventLoopImplementation.runInTaskRunnerQueue.enqueue(new CallbackHelper(&tester)); 5539 5540 5541 foreach(ref thread; pool) { 5542 thread.join(); 5543 } 5544 +/ 5545 5546 5547 static int received; 5548 5549 static void tester() { 5550 received++; 5551 //writeln(cast(void*) Thread.getThis, " ", received); 5552 } 5553 5554 5555 5556 auto ev = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 5557 foreach(i; 0 .. 100) 5558 ev.thisThreadQueue.enqueue(new CallbackHelper(&tester)); 5559 foreach(i; 0 .. 100 / 16 + 1) 5560 ev.runOnce(); 5561 import std.conv; 5562 assert(received == 100, to!string(received)); 5563 5564 } 5565 +/ 5566 5567 /++ 5568 This is primarily a helper for the event queues. It is public in the hope it might be useful, 5569 but subject to change without notice; I will treat breaking it the same as if it is private. 5570 (That said, it is a simple little utility that does its job, so it is unlikely to change much. 5571 The biggest change would probably be letting it grow and changing from inline to dynamic array.) 5572 5573 It is a fixed-size ring buffer that synchronizes on a given object you give it in the constructor. 5574 5575 After enqueuing something, you should probably set an event to notify the other threads. This is left 5576 as an exercise to you (or another wrapper). 5577 +/ 5578 struct SynchronizedCircularBuffer(T, size_t maxSize = 128) { 5579 private T[maxSize] ring; 5580 private int front; 5581 private int back; 5582 5583 private Object synchronizedOn; 5584 5585 @disable this(); 5586 5587 /++ 5588 The Object's monitor is used to synchronize the methods in here. 5589 +/ 5590 this(Object synchronizedOn) { 5591 this.synchronizedOn = synchronizedOn; 5592 } 5593 5594 /++ 5595 Note the potential race condition between calling this and actually dequeuing something. You might 5596 want to acquire the lock on the object before calling this (nested synchronized things are allowed 5597 as long as the same thread is the one doing it). 5598 +/ 5599 bool isEmpty() { 5600 synchronized(this.synchronizedOn) { 5601 return front == back; 5602 } 5603 } 5604 5605 /++ 5606 Note the potential race condition between calling this and actually queuing something. 5607 +/ 5608 bool isFull() { 5609 synchronized(this.synchronizedOn) { 5610 return isFullUnsynchronized(); 5611 } 5612 } 5613 5614 private bool isFullUnsynchronized() nothrow const { 5615 return ((back + 1) % ring.length) == front; 5616 5617 } 5618 5619 /++ 5620 If this returns true, you should signal listening threads (with an event or a semaphore, 5621 depending on how you dequeue it). If it returns false, the queue was full and your thing 5622 was NOT added. You might wait and retry later (you could set up another event to signal it 5623 has been read and wait for that, or maybe try on a timer), or just fail and throw an exception 5624 or to abandon the message. 5625 +/ 5626 bool enqueue(T what) { 5627 synchronized(this.synchronizedOn) { 5628 if(isFullUnsynchronized()) 5629 return false; 5630 ring[(back++) % ring.length] = what; 5631 return true; 5632 } 5633 } 5634 5635 private T dequeueUnsynchronized() nothrow { 5636 assert(front != back); 5637 return ring[(front++) % ring.length]; 5638 } 5639 5640 /++ 5641 If you are using a semaphore to signal, you can call this once for each count of it 5642 and you can do that separately from this call (though they should be paired). 5643 5644 If you are using an event, you should use [dequeueSeveral] instead to drain it. 5645 +/ 5646 T dequeue() { 5647 synchronized(this.synchronizedOn) { 5648 return dequeueUnsynchronized(); 5649 } 5650 } 5651 5652 /++ 5653 Note that if you use a semaphore to signal waiting threads, you should probably not call this. 5654 5655 If you use a set/reset event, there's a potential race condition between the dequeue and event 5656 reset. This is why the `runInsideLockIfEmpty` delegate is there - when it is empty, before it 5657 unlocks, it will give you a chance to reset the event. Otherwise, it can remain set to indicate 5658 that there's still pending data in the queue. 5659 +/ 5660 T[] dequeueSeveral(return T[] buffer, scope void delegate() runInsideLockIfEmpty = null) { 5661 int pos; 5662 synchronized(this.synchronizedOn) { 5663 while(pos < buffer.length && front != back) { 5664 buffer[pos++] = dequeueUnsynchronized(); 5665 } 5666 if(front == back && runInsideLockIfEmpty !is null) 5667 runInsideLockIfEmpty(); 5668 } 5669 return buffer[0 .. pos]; 5670 } 5671 } 5672 5673 unittest { 5674 Object object = new Object(); 5675 auto queue = SynchronizedCircularBuffer!CallbackHelper(object); 5676 assert(queue.isEmpty); 5677 foreach(i; 0 .. queue.ring.length - 1) 5678 queue.enqueue(cast(CallbackHelper) cast(void*) i); 5679 assert(queue.isFull); 5680 5681 foreach(i; 0 .. queue.ring.length - 1) 5682 assert(queue.dequeue() is (cast(CallbackHelper) cast(void*) i)); 5683 assert(queue.isEmpty); 5684 5685 foreach(i; 0 .. queue.ring.length - 1) 5686 queue.enqueue(cast(CallbackHelper) cast(void*) i); 5687 assert(queue.isFull); 5688 5689 CallbackHelper[] buffer = new CallbackHelper[](300); 5690 auto got = queue.dequeueSeveral(buffer); 5691 assert(got.length == queue.ring.length - 1); 5692 assert(queue.isEmpty); 5693 foreach(i, item; got) 5694 assert(item is (cast(CallbackHelper) cast(void*) i)); 5695 5696 foreach(i; 0 .. 8) 5697 queue.enqueue(cast(CallbackHelper) cast(void*) i); 5698 buffer = new CallbackHelper[](4); 5699 got = queue.dequeueSeveral(buffer); 5700 assert(got.length == 4); 5701 foreach(i, item; got) 5702 assert(item is (cast(CallbackHelper) cast(void*) i)); 5703 got = queue.dequeueSeveral(buffer); 5704 assert(got.length == 4); 5705 foreach(i, item; got) 5706 assert(item is (cast(CallbackHelper) cast(void*) (i+4))); 5707 got = queue.dequeueSeveral(buffer); 5708 assert(got.length == 0); 5709 assert(queue.isEmpty); 5710 } 5711 5712 /++ 5713 5714 +/ 5715 enum ByteOrder { 5716 irrelevant, 5717 littleEndian, 5718 bigEndian, 5719 } 5720 5721 /++ 5722 A class to help write a stream of binary data to some target. 5723 5724 NOT YET FUNCTIONAL 5725 +/ 5726 class WritableStream { 5727 /++ 5728 5729 +/ 5730 this(size_t bufferSize) { 5731 this(new ubyte[](bufferSize)); 5732 } 5733 5734 /// ditto 5735 this(ubyte[] buffer) { 5736 this.buffer = buffer; 5737 } 5738 5739 /++ 5740 5741 +/ 5742 final void put(T)(T value, ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5743 static if(T.sizeof == 8) 5744 ulong b; 5745 else static if(T.sizeof == 4) 5746 uint b; 5747 else static if(T.sizeof == 2) 5748 ushort b; 5749 else static if(T.sizeof == 1) 5750 ubyte b; 5751 else static assert(0, "unimplemented type, try using just the basic types"); 5752 5753 if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1) 5754 throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte", "WritableStream.put", file, line); 5755 5756 final switch(byteOrder) { 5757 case ByteOrder.irrelevant: 5758 writeOneByte(b); 5759 break; 5760 case ByteOrder.littleEndian: 5761 foreach(i; 0 .. T.sizeof) { 5762 writeOneByte(b & 0xff); 5763 b >>= 8; 5764 } 5765 break; 5766 case ByteOrder.bigEndian: 5767 int amount = T.sizeof * 8 - 8; 5768 foreach(i; 0 .. T.sizeof) { 5769 writeOneByte((b >> amount) & 0xff); 5770 amount -= 8; 5771 } 5772 break; 5773 } 5774 } 5775 5776 /// ditto 5777 final void put(T : E[], E)(T value, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5778 foreach(item; value) 5779 put(item, elementByteOrder, file, line); 5780 } 5781 5782 /++ 5783 Performs a final flush() call, then marks the stream as closed, meaning no further data will be written to it. 5784 +/ 5785 void close() { 5786 isClosed_ = true; 5787 } 5788 5789 /++ 5790 Writes what is currently in the buffer to the target and waits for the target to accept it. 5791 Please note: if you are subclassing this to go to a different target 5792 +/ 5793 void flush() {} 5794 5795 /++ 5796 Returns true if either you closed it or if the receiving end closed their side, indicating they 5797 don't want any more data. 5798 +/ 5799 bool isClosed() { 5800 return isClosed_; 5801 } 5802 5803 // hasRoomInBuffer 5804 // canFlush 5805 // waitUntilCanFlush 5806 5807 // flushImpl 5808 // markFinished / close - tells the other end you're done 5809 5810 private final writeOneByte(ubyte value) { 5811 if(bufferPosition == buffer.length) 5812 flush(); 5813 5814 buffer[bufferPosition++] = value; 5815 } 5816 5817 5818 private { 5819 ubyte[] buffer; 5820 int bufferPosition; 5821 bool isClosed_; 5822 } 5823 } 5824 5825 /++ 5826 A stream can be used by just one task at a time, but one task can consume multiple streams. 5827 5828 Streams may be populated by async sources (in which case they must be called from a fiber task), 5829 from a function generating the data on demand (including an input range), from memory, or from a synchronous file. 5830 5831 A stream of heterogeneous types is compatible with input ranges. 5832 5833 It reads binary data. 5834 +/ 5835 version(HasThread) class ReadableStream { 5836 5837 this() { 5838 5839 } 5840 5841 /++ 5842 Gets data of the specified type `T` off the stream. The byte order of the T on the stream must be specified unless it is irrelevant (e.g. single byte entries). 5843 5844 --- 5845 // get an int out of a big endian stream 5846 int i = stream.get!int(ByteOrder.bigEndian); 5847 5848 // get i bytes off the stream 5849 ubyte[] data = stream.get!(ubyte[])(i); 5850 --- 5851 +/ 5852 final T get(T)(ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5853 if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1) 5854 throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 5855 5856 // FIXME: what if it is a struct? 5857 5858 while(bufferedLength() < T.sizeof) 5859 waitForAdditionalData(); 5860 5861 static if(T.sizeof == 1) { 5862 ubyte ret = consumeOneByte(); 5863 return *cast(T*) &ret; 5864 } else { 5865 static if(T.sizeof == 8) 5866 ulong ret; 5867 else static if(T.sizeof == 4) 5868 uint ret; 5869 else static if(T.sizeof == 2) 5870 ushort ret; 5871 else static assert(0, "unimplemented type, try using just the basic types"); 5872 5873 if(byteOrder == ByteOrder.littleEndian) { 5874 typeof(ret) buffer; 5875 foreach(b; 0 .. T.sizeof) { 5876 buffer = consumeOneByte(); 5877 buffer <<= T.sizeof * 8 - 8; 5878 5879 ret >>= 8; 5880 ret |= buffer; 5881 } 5882 } else { 5883 foreach(b; 0 .. T.sizeof) { 5884 ret <<= 8; 5885 ret |= consumeOneByte(); 5886 } 5887 } 5888 5889 return *cast(T*) &ret; 5890 } 5891 } 5892 5893 /// ditto 5894 final T get(T : E[], E)(size_t length, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5895 if(elementByteOrder == ByteOrder.irrelevant && E.sizeof > 1) 5896 throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 5897 5898 // if the stream is closed before getting the length or the terminator, should we send partial stuff 5899 // or just throw? 5900 5901 while(bufferedLength() < length * E.sizeof) 5902 waitForAdditionalData(); 5903 5904 T ret; 5905 5906 ret.length = length; 5907 5908 if(false && elementByteOrder == ByteOrder.irrelevant) { 5909 // ret[] = 5910 // FIXME: can prolly optimize 5911 } else { 5912 foreach(i; 0 .. length) 5913 ret[i] = get!E(elementByteOrder); 5914 } 5915 5916 return ret; 5917 5918 } 5919 5920 /// ditto 5921 final T get(T : E[], E)(scope bool delegate(E e) isTerminatingSentinel, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 5922 if(byteOrder == ByteOrder.irrelevant && E.sizeof > 1) 5923 throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 5924 5925 assert(0, "Not implemented"); 5926 } 5927 5928 /++ 5929 5930 +/ 5931 bool isClosed() { 5932 return isClosed_; 5933 } 5934 5935 // Control side of things 5936 5937 private bool isClosed_; 5938 5939 /++ 5940 Feeds data into the stream, which can be consumed by `get`. If a task is waiting for more 5941 data to satisfy its get requests, this will trigger those tasks to resume. 5942 5943 If you feed it empty data, it will mark the stream as closed. 5944 +/ 5945 void feedData(ubyte[] data) { 5946 if(data.length == 0) 5947 isClosed_ = true; 5948 5949 currentBuffer = data; 5950 // this is a borrowed buffer, so we won't keep the reference long term 5951 scope(exit) 5952 currentBuffer = null; 5953 5954 if(waitingTask !is null) { 5955 waitingTask.call(); 5956 } 5957 } 5958 5959 /++ 5960 You basically have to use this thing from a task 5961 +/ 5962 protected void waitForAdditionalData() { 5963 Fiber task = Fiber.getThis; 5964 5965 assert(task !is null); 5966 5967 if(waitingTask !is null && waitingTask !is task) 5968 throw new ArsdException!"streams can only have one waiting task"; 5969 5970 // copy any pending data in our buffer to the longer-term buffer 5971 if(currentBuffer.length) 5972 leftoverBuffer ~= currentBuffer; 5973 5974 waitingTask = task; 5975 task.yield(); 5976 } 5977 5978 private Fiber waitingTask; 5979 private ubyte[] leftoverBuffer; 5980 private ubyte[] currentBuffer; 5981 5982 private size_t bufferedLength() { 5983 return leftoverBuffer.length + currentBuffer.length; 5984 } 5985 5986 private ubyte consumeOneByte() { 5987 ubyte b; 5988 if(leftoverBuffer.length) { 5989 b = leftoverBuffer[0]; 5990 leftoverBuffer = leftoverBuffer[1 .. $]; 5991 } else if(currentBuffer.length) { 5992 b = currentBuffer[0]; 5993 currentBuffer = currentBuffer[1 .. $]; 5994 } else { 5995 assert(0, "consuming off an empty buffer is impossible"); 5996 } 5997 5998 return b; 5999 } 6000 } 6001 6002 // FIXME: do a stringstream too 6003 6004 unittest { 6005 auto stream = new ReadableStream(); 6006 6007 int position; 6008 char[16] errorBuffer; 6009 6010 auto fiber = new Fiber(() { 6011 position = 1; 6012 int a = stream.get!int(ByteOrder.littleEndian); 6013 assert(a == 10, intToString(a, errorBuffer[])); 6014 position = 2; 6015 ubyte b = stream.get!ubyte; 6016 assert(b == 33); 6017 position = 3; 6018 6019 // ubyte[] c = stream.get!(ubyte[])(3); 6020 // int[] d = stream.get!(int[])(3); 6021 }); 6022 6023 fiber.call(); 6024 assert(position == 1); 6025 stream.feedData([10, 0, 0, 0]); 6026 assert(position == 2); 6027 stream.feedData([33]); 6028 assert(position == 3); 6029 6030 // stream.feedData([1,2,3]); 6031 // stream.feedData([1,2,3,4,1,2,3,4,1,2,3,4]); 6032 } 6033 6034 /++ 6035 UNSTABLE, NOT FULLY IMPLEMENTED. DO NOT USE YET. 6036 6037 You might use this like: 6038 6039 --- 6040 auto proc = new ExternalProcess(); 6041 auto stdoutStream = new ReadableStream(); 6042 6043 // to use a stream you can make one and have a task consume it 6044 runTask({ 6045 while(!stdoutStream.isClosed) { 6046 auto line = stdoutStream.get!string(e => e == '\n'); 6047 } 6048 }); 6049 6050 // then make the process feed into the stream 6051 proc.onStdoutAvailable = (got) { 6052 stdoutStream.feedData(got); // send it to the stream for processing 6053 stdout.rawWrite(got); // forward it through to our own thing 6054 // could also append it to a buffer to return it on complete 6055 }; 6056 proc.start(); 6057 --- 6058 6059 Please note that this does not currently and I have no plans as of this writing to add support for any kind of direct file descriptor passing. It always pipes them back to the parent for processing. If you don't want this, call the lower level functions yourself; the reason this class is here is to aid integration in the arsd.core event loop. Of course, I might change my mind on this. 6060 6061 Bugs: 6062 Not implemented at all on Windows yet. 6063 +/ 6064 class ExternalProcess /*: AsyncOperationRequest*/ { 6065 6066 private static version(Posix) { 6067 __gshared ExternalProcess[pid_t] activeChildren; 6068 6069 void recordChildCreated(pid_t pid, ExternalProcess proc) { 6070 synchronized(typeid(ExternalProcess)) { 6071 activeChildren[pid] = proc; 6072 } 6073 } 6074 6075 void recordChildTerminated(pid_t pid, int status) { 6076 synchronized(typeid(ExternalProcess)) { 6077 if(pid in activeChildren) { 6078 auto ac = activeChildren[pid]; 6079 ac.completed = true; 6080 ac.status = status; 6081 activeChildren.remove(pid); 6082 } 6083 } 6084 } 6085 } 6086 6087 // FIXME: config to pass through a shell or not 6088 6089 /++ 6090 This is the native version for Windows. 6091 +/ 6092 this(string program, string commandLine) { 6093 version(Posix) { 6094 assert(0, "not implemented command line to posix args yet"); 6095 } 6096 else throw new NotYetImplementedException(); 6097 } 6098 6099 this(string commandLine) { 6100 version(Posix) { 6101 assert(0, "not implemented command line to posix args yet"); 6102 } 6103 else throw new NotYetImplementedException(); 6104 } 6105 6106 this(string[] args) { 6107 version(Posix) { 6108 this.program = FilePath(args[0]); 6109 this.args = args; 6110 } 6111 else throw new NotYetImplementedException(); 6112 } 6113 6114 /++ 6115 This is the native version for Posix. 6116 +/ 6117 this(FilePath program, string[] args) { 6118 version(Posix) { 6119 this.program = program; 6120 this.args = args; 6121 } 6122 else throw new NotYetImplementedException(); 6123 } 6124 6125 // you can modify these before calling start 6126 int stdoutBufferSize = 32 * 1024; 6127 int stderrBufferSize = 8 * 1024; 6128 6129 void start() { 6130 version(Posix) { 6131 int ret; 6132 6133 int[2] stdinPipes; 6134 ret = pipe(stdinPipes); 6135 if(ret == -1) 6136 throw new ErrnoApiException("stdin pipe", errno); 6137 6138 scope(failure) { 6139 close(stdinPipes[0]); 6140 close(stdinPipes[1]); 6141 } 6142 6143 stdinFd = stdinPipes[1]; 6144 6145 int[2] stdoutPipes; 6146 ret = pipe(stdoutPipes); 6147 if(ret == -1) 6148 throw new ErrnoApiException("stdout pipe", errno); 6149 6150 scope(failure) { 6151 close(stdoutPipes[0]); 6152 close(stdoutPipes[1]); 6153 } 6154 6155 stdoutFd = stdoutPipes[0]; 6156 6157 int[2] stderrPipes; 6158 ret = pipe(stderrPipes); 6159 if(ret == -1) 6160 throw new ErrnoApiException("stderr pipe", errno); 6161 6162 scope(failure) { 6163 close(stderrPipes[0]); 6164 close(stderrPipes[1]); 6165 } 6166 6167 stderrFd = stderrPipes[0]; 6168 6169 6170 int[2] errorReportPipes; 6171 ret = pipe(errorReportPipes); 6172 if(ret == -1) 6173 throw new ErrnoApiException("error reporting pipe", errno); 6174 6175 scope(failure) { 6176 close(errorReportPipes[0]); 6177 close(errorReportPipes[1]); 6178 } 6179 6180 setCloExec(errorReportPipes[0]); 6181 setCloExec(errorReportPipes[1]); 6182 6183 auto forkRet = fork(); 6184 if(forkRet == -1) 6185 throw new ErrnoApiException("fork", errno); 6186 6187 if(forkRet == 0) { 6188 // child side 6189 6190 // FIXME can we do more error checking that is actually useful here? 6191 // these operations are virtually guaranteed to succeed given the setup anyway. 6192 6193 // FIXME pty too 6194 6195 void fail(int step) { 6196 import core.stdc.errno; 6197 auto code = errno; 6198 6199 // report the info back to the parent then exit 6200 6201 int[2] msg = [step, code]; 6202 auto ret = write(errorReportPipes[1], msg.ptr, msg.sizeof); 6203 6204 // but if this fails there's not much we can do... 6205 6206 import core.stdc.stdlib; 6207 exit(1); 6208 } 6209 6210 // dup2 closes the fd it is replacing automatically 6211 dup2(stdinPipes[0], 0); 6212 dup2(stdoutPipes[1], 1); 6213 dup2(stderrPipes[1], 2); 6214 6215 // don't need either of the original pipe fds anymore 6216 close(stdinPipes[0]); 6217 close(stdinPipes[1]); 6218 close(stdoutPipes[0]); 6219 close(stdoutPipes[1]); 6220 close(stderrPipes[0]); 6221 close(stderrPipes[1]); 6222 6223 // the error reporting pipe will be closed upon exec since we set cloexec before fork 6224 // and everything else should have cloexec set too hopefully. 6225 6226 if(beforeExec) 6227 beforeExec(); 6228 6229 // i'm not sure that a fully-initialized druntime is still usable 6230 // after a fork(), so i'm gonna stick to the C lib in here. 6231 6232 const(char)* file = mallocedStringz(program.path).ptr; 6233 if(file is null) 6234 fail(1); 6235 const(char)*[] argv = mallocSlice!(const(char)*)(args.length + 1); 6236 if(argv is null) 6237 fail(2); 6238 foreach(idx, arg; args) { 6239 argv[idx] = mallocedStringz(args[idx]).ptr; 6240 if(argv[idx] is null) 6241 fail(3); 6242 } 6243 argv[args.length] = null; 6244 6245 auto rete = execvp/*e*/(file, argv.ptr/*, envp*/); 6246 if(rete == -1) { 6247 fail(4); 6248 } else { 6249 // unreachable code, exec never returns if it succeeds 6250 assert(0); 6251 } 6252 } else { 6253 pid = forkRet; 6254 6255 recordChildCreated(pid, this); 6256 6257 // close our copy of the write side of the error reporting pipe 6258 // so the read will immediately give eof when the fork closes it too 6259 ErrnoEnforce!close(errorReportPipes[1]); 6260 6261 int[2] msg; 6262 // this will block to wait for it to actually either start up or fail to exec (which should be near instant) 6263 auto val = read(errorReportPipes[0], msg.ptr, msg.sizeof); 6264 6265 if(val == -1) 6266 throw new ErrnoApiException("read error report", errno); 6267 6268 if(val == msg.sizeof) { 6269 // error happened 6270 // FIXME: keep the step part of the error report too 6271 throw new ErrnoApiException("exec", msg[1]); 6272 } else if(val == 0) { 6273 // pipe closed, meaning exec succeeded 6274 } else { 6275 assert(0); // never supposed to happen 6276 } 6277 6278 // set the ones we keep to close upon future execs 6279 // FIXME should i set NOBLOCK at this time too? prolly should 6280 setCloExec(stdinPipes[1]); 6281 setCloExec(stdoutPipes[0]); 6282 setCloExec(stderrPipes[0]); 6283 6284 // and close the others 6285 ErrnoEnforce!close(stdinPipes[0]); 6286 ErrnoEnforce!close(stdoutPipes[1]); 6287 ErrnoEnforce!close(stderrPipes[1]); 6288 6289 ErrnoEnforce!close(errorReportPipes[0]); 6290 6291 // and now register the ones we need to read with the event loop so it can call the callbacks 6292 // also need to listen to SIGCHLD to queue up the terminated callback. FIXME 6293 6294 stdoutUnregisterToken = getThisThreadEventLoop().addCallbackOnFdReadable(stdoutFd, new CallbackHelper(&stdoutReadable)); 6295 stderrUnregisterToken = getThisThreadEventLoop().addCallbackOnFdReadable(stderrFd, new CallbackHelper(&stderrReadable)); 6296 } 6297 } 6298 } 6299 6300 private version(Posix) { 6301 import core.sys.posix.unistd; 6302 import core.sys.posix.fcntl; 6303 6304 int stdinFd = -1; 6305 int stdoutFd = -1; 6306 int stderrFd = -1; 6307 6308 ICoreEventLoop.UnregisterToken stdoutUnregisterToken; 6309 ICoreEventLoop.UnregisterToken stderrUnregisterToken; 6310 6311 pid_t pid = -1; 6312 6313 public void delegate() beforeExec; 6314 6315 FilePath program; 6316 string[] args; 6317 6318 void stdoutReadable() { 6319 if(stdoutReadBuffer is null) 6320 stdoutReadBuffer = new ubyte[](stdoutBufferSize); 6321 auto ret = read(stdoutFd, stdoutReadBuffer.ptr, stdoutReadBuffer.length); 6322 if(ret == -1) 6323 throw new ErrnoApiException("read", errno); 6324 if(onStdoutAvailable) { 6325 onStdoutAvailable(stdoutReadBuffer[0 .. ret]); 6326 } 6327 6328 if(ret == 0) { 6329 stdoutUnregisterToken.unregister(); 6330 6331 close(stdoutFd); 6332 stdoutFd = -1; 6333 } 6334 } 6335 6336 void stderrReadable() { 6337 if(stderrReadBuffer is null) 6338 stderrReadBuffer = new ubyte[](stderrBufferSize); 6339 auto ret = read(stderrFd, stderrReadBuffer.ptr, stderrReadBuffer.length); 6340 if(ret == -1) 6341 throw new ErrnoApiException("read", errno); 6342 if(onStderrAvailable) { 6343 onStderrAvailable(stderrReadBuffer[0 .. ret]); 6344 } 6345 6346 if(ret == 0) { 6347 stderrUnregisterToken.unregister(); 6348 6349 close(stderrFd); 6350 stderrFd = -1; 6351 } 6352 } 6353 } 6354 6355 private ubyte[] stdoutReadBuffer; 6356 private ubyte[] stderrReadBuffer; 6357 6358 void waitForCompletion() { 6359 getThisThreadEventLoop().run(&this.isComplete); 6360 } 6361 6362 bool isComplete() { 6363 return completed; 6364 } 6365 6366 bool completed; 6367 int status = int.min; 6368 6369 /++ 6370 If blocking, it will block the current task until the write succeeds. 6371 6372 Write `null` as data to close the pipe. Once the pipe is closed, you must not try to write to it again. 6373 +/ 6374 void writeToStdin(in void[] data) { 6375 version(Posix) { 6376 if(data is null) { 6377 close(stdinFd); 6378 stdinFd = -1; 6379 } else { 6380 // FIXME: check the return value again and queue async writes 6381 auto ret = write(stdinFd, data.ptr, data.length); 6382 if(ret == -1) 6383 throw new ErrnoApiException("write", errno); 6384 } 6385 } 6386 6387 } 6388 6389 void delegate(ubyte[] got) onStdoutAvailable; 6390 void delegate(ubyte[] got) onStderrAvailable; 6391 void delegate(int code) onTermination; 6392 6393 // pty? 6394 } 6395 6396 // FIXME: comment this out 6397 /+ 6398 unittest { 6399 auto proc = new ExternalProcess(FilePath("/bin/cat"), ["/bin/cat"]); 6400 6401 getThisThreadEventLoop(); // initialize it 6402 6403 int c = 0; 6404 proc.onStdoutAvailable = delegate(ubyte[] got) { 6405 if(c == 0) 6406 assert(cast(string) got == "hello!"); 6407 else 6408 assert(got.length == 0); 6409 // import std.stdio; writeln(got); 6410 c++; 6411 }; 6412 6413 proc.start(); 6414 6415 assert(proc.pid != -1); 6416 6417 6418 import std.stdio; 6419 Thread[4] pool; 6420 6421 bool shouldExit; 6422 6423 static int received; 6424 6425 proc.writeToStdin("hello!"); 6426 proc.writeToStdin(null); // closes the pipe 6427 6428 proc.waitForCompletion(); 6429 6430 assert(proc.status == 0); 6431 6432 assert(c == 2); 6433 6434 // writeln("here"); 6435 } 6436 +/ 6437 6438 // to test the thundering herd on signal handling 6439 version(none) 6440 unittest { 6441 Thread[4] pool; 6442 foreach(ref thread; pool) { 6443 thread = new class Thread { 6444 this() { 6445 super({ 6446 int count; 6447 getThisThreadEventLoop().run(() { 6448 if(count > 4) return true; 6449 count++; 6450 return false; 6451 }); 6452 }); 6453 } 6454 }; 6455 thread.start(); 6456 } 6457 foreach(ref thread; pool) { 6458 thread.join(); 6459 } 6460 } 6461 6462 /+ 6463 ================= 6464 STDIO REPLACEMENT 6465 ================= 6466 +/ 6467 6468 private void appendToBuffer(ref char[] buffer, ref int pos, scope const(char)[] what) { 6469 auto required = pos + what.length; 6470 if(buffer.length < required) 6471 buffer.length = required; 6472 buffer[pos .. pos + what.length] = what[]; 6473 pos += what.length; 6474 } 6475 6476 private void appendToBuffer(ref char[] buffer, ref int pos, long what) { 6477 if(buffer.length < pos + 16) 6478 buffer.length = pos + 16; 6479 auto sliced = intToString(what, buffer[pos .. $]); 6480 pos += sliced.length; 6481 } 6482 6483 /++ 6484 A `writeln` that actually works, at least for some basic types. 6485 6486 It works correctly on Windows, using the correct functions to write unicode to the console. even allocating a console if needed. If the output has been redirected to a file or pipe, it writes UTF-8. 6487 6488 This always does text. See also WritableStream and WritableTextStream when they are implemented. 6489 +/ 6490 void writeln(T...)(T t) { 6491 char[256] bufferBacking; 6492 char[] buffer = bufferBacking[]; 6493 int pos; 6494 6495 foreach(arg; t) { 6496 static if(is(typeof(arg) : const char[])) { 6497 appendToBuffer(buffer, pos, arg); 6498 } else static if(is(typeof(arg) : stringz)) { 6499 appendToBuffer(buffer, pos, arg.borrow); 6500 } else static if(is(typeof(arg) : long)) { 6501 appendToBuffer(buffer, pos, arg); 6502 } else static if(is(typeof(arg.toString()) : const char[])) { 6503 appendToBuffer(buffer, pos, arg.toString()); 6504 } else { 6505 appendToBuffer(buffer, pos, "<" ~ typeof(arg).stringof ~ ">"); 6506 } 6507 } 6508 6509 appendToBuffer(buffer, pos, "\n"); 6510 6511 actuallyWriteToStdout(buffer[0 .. pos]); 6512 } 6513 6514 private void actuallyWriteToStdout(scope char[] buffer) @trusted { 6515 6516 version(UseStdioWriteln) 6517 { 6518 import std.stdio; 6519 writeln(buffer); 6520 } 6521 else version(Windows) { 6522 import core.sys.windows.wincon; 6523 6524 auto hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 6525 if(hStdOut == null || hStdOut == INVALID_HANDLE_VALUE) { 6526 AllocConsole(); 6527 hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); 6528 } 6529 6530 if(GetFileType(hStdOut) == FILE_TYPE_CHAR) { 6531 wchar[256] wbuffer; 6532 auto toWrite = makeWindowsString(buffer, wbuffer, WindowsStringConversionFlags.convertNewLines); 6533 6534 DWORD written; 6535 WriteConsoleW(hStdOut, toWrite.ptr, cast(DWORD) toWrite.length, &written, null); 6536 } else { 6537 DWORD written; 6538 WriteFile(hStdOut, buffer.ptr, cast(DWORD) buffer.length, &written, null); 6539 } 6540 } else { 6541 import unix = core.sys.posix.unistd; 6542 unix.write(1, buffer.ptr, buffer.length); 6543 } 6544 } 6545 6546 /+ 6547 6548 STDIO 6549 6550 /++ 6551 Please note using this will create a compile-time dependency on [arsd.terminal] 6552 6553 6554 6555 so my writeln replacement: 6556 6557 1) if the std output handle is null, alloc one 6558 2) if it is a character device, write out the proper Unicode text. 6559 3) otherwise write out UTF-8.... maybe with a BOM but maybe not. it is tricky to know what the other end of a pipe expects... 6560 [8:15 AM] 6561 im actually tempted to make the write binary to stdout functions throw an exception if it is a character console / interactive terminal instead of letting you spam it right out 6562 [8:16 AM] 6563 of course you can still cheat by casting binary data to string and using the write string function (and this might be appropriate sometimes) but there kinda is a legit difference between a text output and a binary output device 6564 6565 Stdout can represent either 6566 6567 +/ 6568 void writeln(){} { 6569 6570 } 6571 6572 stderr? 6573 6574 /++ 6575 Please note using this will create a compile-time dependency on [arsd.terminal] 6576 6577 It can be called from a task. 6578 6579 It works correctly on Windows and is user friendly on Linux (using arsd.terminal.getline) 6580 while also working if stdin has been redirected (where arsd.terminal itself would throw) 6581 6582 6583 so say you run a program on an interactive terminal. the program tries to open the stdin binary stream 6584 6585 instead of throwing, the prompt could change to indicate the binary data is expected and you can feed it in either by typing it up,,,, or running some command like maybe <file.bin to have the library do what the shell would have done and feed that to the rest of the program 6586 6587 +/ 6588 string readln()() { 6589 6590 } 6591 6592 6593 // if using stdio as a binary output thing you can pretend it is a file w/ stream capability 6594 struct File { 6595 WritableStream ostream; 6596 ReadableStream istream; 6597 6598 ulong tell; 6599 void seek(ulong to) {} 6600 6601 void sync(); 6602 void close(); 6603 } 6604 6605 // these are a bit special because if it actually is an interactive character device, it might be different than other files and even different than other pipes. 6606 WritableStream stdoutStream() { return null; } 6607 WritableStream stderrStream() { return null; } 6608 ReadableStream stdinStream() { return null; } 6609 6610 +/ 6611 6612 6613 /+ 6614 6615 6616 /+ 6617 Druntime appears to have stuff for darwin, freebsd. I might have to add some for openbsd here and maybe netbsd if i care to test it. 6618 +/ 6619 6620 /+ 6621 6622 arsd_core_init(number_of_worker_threads) 6623 6624 Building-block things wanted for the event loop integration: 6625 * ui 6626 * windows 6627 * terminal / console 6628 * generic 6629 * adopt fd 6630 * adopt windows handle 6631 * shared lib 6632 * load 6633 * timers (relative and real time) 6634 * create 6635 * update 6636 * cancel 6637 * file/directory watches 6638 * file created 6639 * file deleted 6640 * file modified 6641 * file ops 6642 * open 6643 * close 6644 * read 6645 * write 6646 * seek 6647 * sendfile on linux, TransmitFile on Windows 6648 * let completion handlers run in the io worker thread instead of signaling back 6649 * pipe ops (anonymous or named) 6650 * create 6651 * read 6652 * write 6653 * get info about other side of the pipe 6654 * network ops (stream + datagram, ip, ipv6, unix) 6655 * address look up 6656 * connect 6657 * start tls 6658 * listen 6659 * send 6660 * receive 6661 * get peer info 6662 * process ops 6663 * spawn 6664 * notifications when it is terminated or fork or execs 6665 * send signal 6666 * i/o pipes 6667 * thread ops (isDaemon?) 6668 * spawn 6669 * talk to its event loop 6670 * termination notification 6671 * signals 6672 * ctrl+c is the only one i really care about but the others might be made available too. sigchld needs to be done as an impl detail of process ops. 6673 * custom messages 6674 * should be able to send messages from finalizers... 6675 6676 * want to make sure i can stream stuff on top of it all too. 6677 6678 ======== 6679 6680 These things all refer back to a task-local thing that queues the tasks. If it is a fiber, it uses that 6681 and if it is a thread it uses that... 6682 6683 tls IArsdCoreEventLoop curentTaskInterface; // this yields on the wait for calls. the fiber swapper will swap this too. 6684 tls IArsdCoreEventLoop currentThreadInterface; // this blocks on the event loop 6685 6686 shared IArsdCoreEventLoop currentProcessInterface; // this dispatches to any available thread 6687 +/ 6688 6689 6690 /+ 6691 You might have configurable tasks that do not auto-start, e.g. httprequest. maybe @mustUse on those 6692 6693 then some that do auto-start, e.g. setTimeout 6694 6695 6696 timeouts: duration, MonoTime, or SysTime? duration is just a timer monotime auto-adjusts the when, systime sets a real time timerfd 6697 6698 tasks can be set to: 6699 thread affinity - this, any, specific reference 6700 reports to - defaults to this, can also pass down a parent reference. if reports to dies, all its subordinates are cancelled. 6701 6702 6703 you can send a message to a task... maybe maybe just to a task runner (which is itself a task?) 6704 6705 auto file = readFile(x); 6706 auto timeout = setTimeout(y); 6707 auto completed = waitForFirstToCompleteThenCancelOthers(file, timeout); 6708 if(completed == 0) { 6709 file.... 6710 } else { 6711 timeout.... 6712 } 6713 6714 /+ 6715 A task will run on a thread (with possible migration), and report to a task. 6716 +/ 6717 6718 // a compute task is run on a helper thread 6719 auto task = computeTask((shared(bool)* cancellationRequested) { 6720 // or pass in a yield thing... prolly a TaskController which has cancellationRequested and yield controls as well as send message to parent (sync or async) 6721 6722 // you'd periodically send messages back to the parent 6723 }, RunOn.AnyAvailable, Affinity.CanMigrate); 6724 6725 auto task = Task((TaskController controller) { 6726 foreach(x, 0 .. 1000) { 6727 if(x % 10 == 0) 6728 controller.yield(); // periodically yield control, which also checks for cancellation for us 6729 // do some work 6730 6731 controller.sendMessage(...); 6732 controller.sendProgress(x); // yields it for a foreach stream kind of thing 6733 } 6734 6735 return something; // automatically sends the something as the result in a TaskFinished message 6736 }); 6737 6738 foreach(item; task) // waitsForProgress, sendProgress sends an item and the final return sends an item 6739 {} 6740 6741 6742 see ~/test/task.d 6743 6744 // an io task is run locally via the event loops 6745 auto task2 = ioTask(() { 6746 6747 }); 6748 6749 6750 6751 waitForEvent 6752 +/ 6753 6754 /+ 6755 Most functions should prolly take a thread arg too, which defaults 6756 to this thread, but you can also pass it a reference, or a "any available" thing. 6757 6758 This can be a ufcs overload 6759 +/ 6760 6761 interface SemiSynchronousTask { 6762 6763 } 6764 6765 struct TimeoutCompletionResult { 6766 bool completed; 6767 6768 bool opCast(T : bool)() { 6769 return completed; 6770 } 6771 } 6772 6773 struct Timeout { 6774 void reschedule(Duration when) { 6775 6776 } 6777 6778 void cancel() { 6779 6780 } 6781 6782 TimeoutCompletionResult waitForCompletion() { 6783 return TimeoutCompletionResult(false); 6784 } 6785 } 6786 6787 Timeout setTimeout(void delegate() dg, int msecs, int permittedJitter = 20) { 6788 return Timeout.init; 6789 } 6790 6791 void clearTimeout(Timeout timeout) { 6792 timeout.cancel(); 6793 } 6794 6795 void createInterval() {} 6796 void clearInterval() {} 6797 6798 /++ 6799 Schedules a task at the given wall clock time. 6800 +/ 6801 void scheduleTask() {} 6802 6803 struct IoOperationCompletionResult { 6804 enum Status { 6805 cancelled, 6806 completed 6807 } 6808 6809 Status status; 6810 6811 int error; 6812 int bytesWritten; 6813 6814 bool opCast(T : bool)() { 6815 return status == Status.completed; 6816 } 6817 } 6818 6819 struct IoOperation { 6820 void cancel() {} 6821 6822 IoOperationCompletionResult waitForCompletion() { 6823 return IoOperationCompletionResult.init; 6824 } 6825 6826 // could contain a scoped class in here too so it stack allocated 6827 } 6828 6829 // Should return both the object and the index in the array! 6830 Result waitForFirstToComplete(Operation[]...) {} 6831 6832 IoOperation read(IoHandle handle, ubyte[] buffer 6833 6834 /+ 6835 class IoOperation {} 6836 6837 // an io operation and its buffer must not be modified or freed 6838 // in between a call to enqueue and a call to waitForCompletion 6839 // if you used the whenComplete callback, make sure it is NOT gc'd or scope thing goes out of scope in the mean time 6840 // if its dtor runs, it'd be forced to be cancelled... 6841 6842 scope IoOperation op = new IoOperation(buffer_size); 6843 op.start(); 6844 op.waitForCompletion(); 6845 +/ 6846 6847 /+ 6848 will want: 6849 read, write 6850 send, recv 6851 6852 cancel 6853 6854 open file, open (named or anonymous) pipe, open process 6855 connect, accept 6856 SSL 6857 close 6858 6859 postEvent 6860 postAPC? like run in gui thread / async 6861 waitForEvent ? needs to handle a timeout and a cancellation. would only work in the fiber task api. 6862 6863 waitForSuccess 6864 6865 interrupt handler 6866 6867 onPosixReadReadiness 6868 onPosixWriteReadiness 6869 6870 onWindowsHandleReadiness 6871 - but they're one-offs so you gotta reregister for each event 6872 +/ 6873 6874 6875 6876 /+ 6877 arsd.core.uda 6878 6879 you define a model struct with the types you want to extract 6880 6881 you get it with like Model extract(Model, UDAs...)(Model default) 6882 6883 defaultModel!alias > defaultModel!Type(defaultModel("identifier")) 6884 6885 6886 6887 6888 6889 6890 6891 6892 6893 6894 so while i laid there sleep deprived i did think a lil more on some uda stuff. it isn't especially novel but a combination of a few other techniques 6895 6896 you might be like 6897 6898 struct MyUdas { 6899 DbName name; 6900 DbIgnore ignore; 6901 } 6902 6903 elsewhere 6904 6905 foreach(alias; allMembers) { 6906 auto udas = getUdas!(MyUdas, __traits(getAttributes, alias))(MyUdas(DbName(__traits(identifier, alias)))); 6907 } 6908 6909 6910 so you pass the expected type and the attributes as the template params, then the runtime params are the default values for the given types 6911 6912 so what the thing does essentially is just sets the values of the given thing to the udas based on type then returns the modified instance 6913 6914 so the end result is you keep the last ones. it wouldn't report errors if multiple things added but it p simple to understand, simple to document (even though the default values are not in the struct itself, you can put ddocs in them), and uses the tricks to minimize generated code size 6915 +/ 6916 6917 +/ 6918 6919 package(arsd) version(Windows) extern(Windows) { 6920 BOOL CancelIoEx(HANDLE, LPOVERLAPPED); 6921 6922 struct WSABUF { 6923 ULONG len; 6924 ubyte* buf; 6925 } 6926 alias LPWSABUF = WSABUF*; 6927 6928 // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-wsaoverlapped 6929 // "The WSAOVERLAPPED structure is compatible with the Windows OVERLAPPED structure." 6930 // so ima lie here in the bindings. 6931 6932 int WSASend(SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6933 int WSASendTo(SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, const sockaddr*, int, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6934 6935 int WSARecv(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6936 int WSARecvFrom(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, sockaddr*, LPINT, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 6937 } 6938 6939 package(arsd) version(OSXCocoa) { 6940 6941 /* Copy/paste chunk from Jacob Carlborg { */ 6942 // from https://raw.githubusercontent.com/jacob-carlborg/druntime/550edd0a64f0eb2c4f35d3ec3d88e26b40ac779e/src/core/stdc/clang_block.d 6943 // with comments stripped (see docs in the original link), code reformatted, and some names changed to avoid potential conflicts 6944 6945 import core.stdc.config; 6946 struct ObjCBlock(R = void, Params...) { 6947 private: 6948 alias extern(C) R function(ObjCBlock*, Params) Invoke; 6949 6950 void* isa; 6951 int flags; 6952 int reserved = 0; 6953 Invoke invoke; 6954 Descriptor* descriptor; 6955 6956 // Imported variables go here 6957 R delegate(Params) dg; 6958 6959 this(void* isa, int flags, Invoke invoke, R delegate(Params) dg) { 6960 this.isa = isa; 6961 this.flags = flags; 6962 this.invoke = invoke; 6963 this.dg = dg; 6964 this.descriptor = &.objcblock_descriptor; 6965 } 6966 } 6967 ObjCBlock!(R, Params) block(R, Params...)(R delegate(Params) dg) { 6968 static if (Params.length == 0) 6969 enum flags = 0x50000000; 6970 else 6971 enum flags = 0x40000000; 6972 6973 return ObjCBlock!(R, Params)(&_NSConcreteStackBlock, flags, &objcblock_invoke!(R, Params), dg); 6974 } 6975 6976 private struct Descriptor { 6977 c_ulong reserved; 6978 c_ulong size; 6979 const(char)* signature; 6980 } 6981 private extern(C) extern __gshared void*[32] _NSConcreteStackBlock; 6982 private __gshared auto objcblock_descriptor = Descriptor(0, ObjCBlock!().sizeof); 6983 private extern(C) R objcblock_invoke(R, Args...)(ObjCBlock!(R, Args)* block, Args args) { 6984 return block.dg(args); 6985 } 6986 6987 6988 /* End copy/paste chunk from Jacob Carlborg } */ 6989 6990 6991 /+ 6992 To let Cocoa know that you intend to use multiple threads, all you have to do is spawn a single thread using the NSThread class and let that thread immediately exit. Your thread entry point need not do anything. Just the act of spawning a thread using NSThread is enough to ensure that the locks needed by the Cocoa frameworks are put in place. 6993 6994 If you are not sure if Cocoa thinks your application is multithreaded or not, you can use the isMultiThreaded method of NSThread to check. 6995 +/ 6996 6997 6998 struct DeifiedNSString { 6999 char[16] sso; 7000 const(char)[] str; 7001 7002 this(NSString s) { 7003 auto len = s.length; 7004 if(len <= sso.length / 4) 7005 str = sso[]; 7006 else 7007 str = new char[](len * 4); 7008 7009 NSUInteger count; 7010 NSRange leftover; 7011 auto ret = s.getBytes(cast(char*) str.ptr, str.length, &count, NSStringEncoding.NSUTF8StringEncoding, NSStringEncodingConversionOptions.none, NSRange(0, len), &leftover); 7012 if(ret) 7013 str = str[0 .. count]; 7014 else 7015 throw new Exception("uh oh"); 7016 } 7017 } 7018 7019 extern (Objective-C) { 7020 import core.attribute; // : selector, optional; 7021 7022 alias NSUInteger = size_t; 7023 alias NSInteger = ptrdiff_t; 7024 alias unichar = wchar; 7025 struct SEL_; 7026 alias SEL_* SEL; 7027 // this is called plain `id` in objective C but i fear mistakes with that in D. like sure it is a type instead of a variable like most things called id but i still think it is weird. i might change my mind later. 7028 alias void* NSid; // FIXME? the docs say this is a pointer to an instance of a class, but that is not necessary a child of NSObject 7029 7030 extern class NSObject { 7031 static NSObject alloc() @selector("alloc"); 7032 NSObject init() @selector("init"); 7033 7034 void retain() @selector("retain"); 7035 void release() @selector("release"); 7036 void autorelease() @selector("autorelease"); 7037 7038 void performSelectorOnMainThread(SEL aSelector, NSid arg, bool waitUntilDone) @selector("performSelectorOnMainThread:withObject:waitUntilDone:"); 7039 } 7040 7041 // this is some kind of generic in objc... 7042 extern class NSArray : NSObject { 7043 static NSArray arrayWithObjects(NSid* objects, NSUInteger count) @selector("arrayWithObjects:count:"); 7044 } 7045 7046 extern class NSString : NSObject { 7047 override static NSString alloc() @selector("alloc"); 7048 override NSString init() @selector("init"); 7049 7050 NSString initWithUTF8String(const scope char* str) @selector("initWithUTF8String:"); 7051 7052 NSString initWithBytes( 7053 const(ubyte)* bytes, 7054 NSUInteger length, 7055 NSStringEncoding encoding 7056 ) @selector("initWithBytes:length:encoding:"); 7057 7058 unichar characterAtIndex(NSUInteger index) @selector("characterAtIndex:"); 7059 NSUInteger length() @selector("length"); 7060 const char* UTF8String() @selector("UTF8String"); 7061 7062 void getCharacters(wchar* buffer, NSRange range) @selector("getCharacters:range:"); 7063 7064 bool getBytes(void* buffer, NSUInteger maxBufferCount, NSUInteger* usedBufferCount, NSStringEncoding encoding, NSStringEncodingConversionOptions options, NSRange range, NSRange* leftover) @selector("getBytes:maxLength:usedLength:encoding:options:range:remainingRange:"); 7065 } 7066 7067 struct NSRange { 7068 NSUInteger loc; 7069 NSUInteger len; 7070 } 7071 7072 enum NSStringEncodingConversionOptions : NSInteger { 7073 none = 0, 7074 NSAllowLossyEncodingConversion = 1, 7075 NSExternalRepresentationEncodingConversion = 2 7076 } 7077 7078 enum NSEventType { 7079 idk 7080 7081 } 7082 7083 enum NSEventModifierFlags : NSUInteger { 7084 NSEventModifierFlagCapsLock = 1 << 16, 7085 NSEventModifierFlagShift = 1 << 17, 7086 NSEventModifierFlagControl = 1 << 18, 7087 NSEventModifierFlagOption = 1 << 19, // aka Alt 7088 NSEventModifierFlagCommand = 1 << 20, // aka super 7089 NSEventModifierFlagNumericPad = 1 << 21, 7090 NSEventModifierFlagHelp = 1 << 22, 7091 NSEventModifierFlagFunction = 1 << 23, 7092 NSEventModifierFlagDeviceIndependentFlagsMask = 0xffff0000UL 7093 } 7094 7095 extern class NSEvent : NSObject { 7096 NSEventType type() @selector("type"); 7097 7098 NSPoint locationInWindow() @selector("locationInWindow"); 7099 NSTimeInterval timestamp() @selector("timestamp"); 7100 NSWindow window() @selector("window"); // note: nullable 7101 NSEventModifierFlags modifierFlags() @selector("modifierFlags"); 7102 7103 NSString characters() @selector("characters"); 7104 NSString charactersIgnoringModifiers() @selector("charactersIgnoringModifiers"); 7105 ushort keyCode() @selector("keyCode"); 7106 ushort specialKey() @selector("specialKey"); 7107 7108 static NSUInteger pressedMouseButtons() @selector("pressedMouseButtons"); 7109 NSPoint locationInWindow() @selector("locationInWindow"); // in screen coordinates 7110 static NSPoint mouseLocation() @selector("mouseLocation"); // in screen coordinates 7111 NSInteger buttonNumber() @selector("buttonNumber"); 7112 7113 CGFloat deltaX() @selector("deltaX"); 7114 CGFloat deltaY() @selector("deltaY"); 7115 CGFloat deltaZ() @selector("deltaZ"); 7116 7117 bool hasPreciseScrollingDeltas() @selector("hasPreciseScrollingDeltas"); 7118 7119 CGFloat scrollingDeltaX() @selector("scrollingDeltaX"); 7120 CGFloat scrollingDeltaY() @selector("scrollingDeltaY"); 7121 7122 // @property(getter=isDirectionInvertedFromDevice, readonly) BOOL directionInvertedFromDevice; 7123 } 7124 7125 extern /* final */ class NSTimer : NSObject { // the docs say don't subclass this, but making it final breaks the bridge 7126 override static NSTimer alloc() @selector("alloc"); 7127 override NSTimer init() @selector("init"); 7128 7129 static NSTimer schedule(NSTimeInterval timeIntervalInSeconds, NSid target, SEL selector, NSid userInfo, bool repeats) @selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"); 7130 7131 void fire() @selector("fire"); 7132 void invalidate() @selector("invalidate"); 7133 7134 bool valid() @selector("isValid"); 7135 // @property(copy) NSDate *fireDate; 7136 NSTimeInterval timeInterval() @selector("timeInterval"); 7137 NSid userInfo() @selector("userInfo"); 7138 7139 NSTimeInterval tolerance() @selector("tolerance"); 7140 NSTimeInterval tolerance(NSTimeInterval) @selector("setTolerance:"); 7141 } 7142 7143 alias NSTimeInterval = double; 7144 7145 extern class NSResponder : NSObject { 7146 NSMenu menu() @selector("menu"); 7147 void menu(NSMenu menu) @selector("setMenu:"); 7148 7149 void keyDown(NSEvent event) @selector("keyDown:"); 7150 void keyUp(NSEvent event) @selector("keyUp:"); 7151 7152 // - (void)interpretKeyEvents:(NSArray<NSEvent *> *)eventArray; 7153 7154 void mouseDown(NSEvent event) @selector("mouseDown:"); 7155 void mouseDragged(NSEvent event) @selector("mouseDragged:"); 7156 void mouseUp(NSEvent event) @selector("mouseUp:"); 7157 void mouseMoved(NSEvent event) @selector("mouseMoved:"); 7158 void mouseEntered(NSEvent event) @selector("mouseEntered:"); 7159 void mouseExited(NSEvent event) @selector("mouseExited:"); 7160 7161 void rightMouseDown(NSEvent event) @selector("rightMouseDown:"); 7162 void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:"); 7163 void rightMouseUp(NSEvent event) @selector("rightMouseUp:"); 7164 7165 void otherMouseDown(NSEvent event) @selector("otherMouseDown:"); 7166 void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:"); 7167 void otherMouseUp(NSEvent event) @selector("otherMouseUp:"); 7168 7169 void scrollWheel(NSEvent event) @selector("scrollWheel:"); 7170 7171 // touch events should also be here btw among others 7172 } 7173 7174 extern class NSApplication : NSResponder { 7175 static NSApplication shared_() @selector("sharedApplication"); 7176 7177 NSApplicationDelegate delegate_() @selector("delegate"); 7178 void delegate_(NSApplicationDelegate) @selector("setDelegate:"); 7179 7180 bool setActivationPolicy(NSApplicationActivationPolicy activationPolicy) @selector("setActivationPolicy:"); 7181 7182 void activateIgnoringOtherApps(bool flag) @selector("activateIgnoringOtherApps:"); 7183 7184 @property NSMenu mainMenu() @selector("mainMenu"); 7185 @property NSMenu mainMenu(NSMenu) @selector("setMainMenu:"); 7186 7187 void run() @selector("run"); 7188 7189 void terminate(void*) @selector("terminate:"); 7190 } 7191 7192 extern interface NSApplicationDelegate { 7193 void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:"); 7194 void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:"); 7195 bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:"); 7196 } 7197 7198 extern class NSNotification : NSObject { 7199 @property NSid object() @selector("object"); 7200 } 7201 7202 enum NSApplicationActivationPolicy : ptrdiff_t { 7203 /* The application is an ordinary app that appears in the Dock and may have a user interface. This is the default for bundled apps, unless overridden in the Info.plist. */ 7204 regular, 7205 7206 /* The application does not appear in the Dock and does not have a menu bar, but it may be activated programmatically or by clicking on one of its windows. This corresponds to LSUIElement=1 in the Info.plist. */ 7207 accessory, 7208 7209 /* The application does not appear in the Dock and may not create windows or be activated. This corresponds to LSBackgroundOnly=1 in the Info.plist. This is also the default for unbundled executables that do not have Info.plists. */ 7210 prohibited 7211 } 7212 7213 extern class NSGraphicsContext : NSObject { 7214 static NSGraphicsContext currentContext() @selector("currentContext"); 7215 NSGraphicsContext graphicsPort() @selector("graphicsPort"); 7216 } 7217 7218 extern class NSMenu : NSObject { 7219 override static NSMenu alloc() @selector("alloc"); 7220 7221 override NSMenu init() @selector("init"); 7222 NSMenu init(NSString title) @selector("initWithTitle:"); 7223 7224 void setSubmenu(NSMenu menu, NSMenuItem item) @selector("setSubmenu:forItem:"); 7225 void addItem(NSMenuItem newItem) @selector("addItem:"); 7226 7227 NSMenuItem addItem( 7228 NSString title, 7229 SEL selector, 7230 NSString charCode 7231 ) @selector("addItemWithTitle:action:keyEquivalent:"); 7232 } 7233 7234 extern class NSMenuItem : NSObject { 7235 override static NSMenuItem alloc() @selector("alloc"); 7236 override NSMenuItem init() @selector("init"); 7237 7238 NSMenuItem init( 7239 NSString title, 7240 SEL selector, 7241 NSString charCode 7242 ) @selector("initWithTitle:action:keyEquivalent:"); 7243 7244 void enabled(bool) @selector("setEnabled:"); 7245 7246 NSResponder target(NSResponder) @selector("setTarget:"); 7247 } 7248 7249 enum NSWindowStyleMask : size_t { 7250 borderless = 0, 7251 titled = 1 << 0, 7252 closable = 1 << 1, 7253 miniaturizable = 1 << 2, 7254 resizable = 1 << 3, 7255 7256 /* Specifies a window with textured background. Textured windows generally don't draw a top border line under the titlebar/toolbar. To get that line, use the NSUnifiedTitleAndToolbarWindowMask mask. 7257 */ 7258 texturedBackground = 1 << 8, 7259 7260 /* Specifies a window whose titlebar and toolbar have a unified look - that is, a continuous background. Under the titlebar and toolbar a horizontal separator line will appear. 7261 */ 7262 unifiedTitleAndToolbar = 1 << 12, 7263 7264 /* When set, the window will appear full screen. This mask is automatically toggled when toggleFullScreen: is called. 7265 */ 7266 fullScreen = 1 << 14, 7267 7268 /* If set, the contentView will consume the full size of the window; it can be combined with other window style masks, but is only respected for windows with a titlebar. 7269 Utilizing this mask opts-in to layer-backing. Utilize the contentLayoutRect or auto-layout contentLayoutGuide to layout views underneath the titlebar/toolbar area. 7270 */ 7271 fullSizeContentView = 1 << 15, 7272 7273 /* The following are only applicable for NSPanel (or a subclass thereof) 7274 */ 7275 utilityWindow = 1 << 4, 7276 docModalWindow = 1 << 6, 7277 nonactivatingPanel = 1 << 7, // Specifies that a panel that does not activate the owning application 7278 hUDWindow = 1 << 13 // Specifies a heads up display panel 7279 } 7280 7281 extern class NSWindow : NSObject { 7282 override static NSWindow alloc() @selector("alloc"); 7283 7284 override NSWindow init() @selector("init"); 7285 7286 NSWindow initWithContentRect( 7287 NSRect contentRect, 7288 NSWindowStyleMask style, 7289 NSBackingStoreType bufferingType, 7290 bool flag 7291 ) @selector("initWithContentRect:styleMask:backing:defer:"); 7292 7293 void makeKeyAndOrderFront(NSid sender) @selector("makeKeyAndOrderFront:"); 7294 NSView contentView() @selector("contentView"); 7295 void contentView(NSView view) @selector("setContentView:"); 7296 void orderFrontRegardless() @selector("orderFrontRegardless"); 7297 void center() @selector("center"); 7298 7299 NSRect frame() @selector("frame"); 7300 7301 NSRect contentRectForFrameRect(NSRect frameRect) @selector("contentRectForFrameRect:"); 7302 7303 NSString title() @selector("title"); 7304 void title(NSString value) @selector("setTitle:"); 7305 7306 void close() @selector("close"); 7307 7308 NSWindowDelegate delegate_() @selector("delegate"); 7309 void delegate_(NSWindowDelegate) @selector("setDelegate:"); 7310 7311 void setBackgroundColor(NSColor color) @selector("setBackgroundColor:"); 7312 } 7313 7314 extern interface NSWindowDelegate { 7315 @optional: 7316 void windowDidResize(NSNotification notification) @selector("windowDidResize:"); 7317 7318 NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:"); 7319 7320 void windowWillClose(NSNotification notification) @selector("windowWillClose:"); 7321 } 7322 7323 extern class NSView : NSResponder { 7324 override NSView init() @selector("init"); 7325 NSView initWithFrame(NSRect frameRect) @selector("initWithFrame:"); 7326 7327 void addSubview(NSView view) @selector("addSubview:"); 7328 7329 bool wantsLayer() @selector("wantsLayer"); 7330 void wantsLayer(bool value) @selector("setWantsLayer:"); 7331 7332 CALayer layer() @selector("layer"); 7333 void uiDelegate(NSObject) @selector("setUIDelegate:"); 7334 7335 void drawRect(NSRect rect) @selector("drawRect:"); 7336 bool isFlipped() @selector("isFlipped"); 7337 bool acceptsFirstResponder() @selector("acceptsFirstResponder"); 7338 bool setNeedsDisplay(bool) @selector("setNeedsDisplay:"); 7339 7340 // DO NOT USE: https://issues.dlang.org/show_bug.cgi?id=19017 7341 // an asm { pop RAX; } after getting the struct can kinda hack around this but still 7342 @property NSRect frame() @selector("frame"); 7343 @property NSRect frame(NSRect rect) @selector("setFrame:"); 7344 7345 void setFrameSize(NSSize newSize) @selector("setFrameSize:"); 7346 void setFrameOrigin(NSPoint newOrigin) @selector("setFrameOrigin:"); 7347 7348 void addSubview(NSView what) @selector("addSubview:"); 7349 void removeFromSuperview() @selector("removeFromSuperview"); 7350 } 7351 7352 extern class NSFont : NSObject { 7353 void set() @selector("set"); // sets it into the current graphics context 7354 void setInContext(NSGraphicsContext context) @selector("setInContext:"); 7355 7356 static NSFont fontWithName(NSString fontName, CGFloat fontSize) @selector("fontWithName:size:"); 7357 // fontWithDescriptor too 7358 // fontWithName and matrix too 7359 static NSFont systemFontOfSize(CGFloat fontSize) @selector("systemFontOfSize:"); 7360 // among others 7361 7362 @property CGFloat pointSize() @selector("pointSize"); 7363 @property bool isFixedPitch() @selector("isFixedPitch"); 7364 // fontDescriptor 7365 @property NSString displayName() @selector("displayName"); 7366 7367 @property CGFloat ascender() @selector("ascender"); 7368 @property CGFloat descender() @selector("descender"); // note it is negative 7369 @property CGFloat capHeight() @selector("capHeight"); 7370 @property CGFloat leading() @selector("leading"); 7371 @property CGFloat xHeight() @selector("xHeight"); 7372 // among many more 7373 } 7374 7375 extern class NSColor : NSObject { 7376 override static NSColor alloc() @selector("alloc"); 7377 static NSColor redColor() @selector("redColor"); 7378 static NSColor whiteColor() @selector("whiteColor"); 7379 7380 CGColorRef CGColor() @selector("CGColor"); 7381 } 7382 7383 extern class CALayer : NSObject { 7384 CGFloat borderWidth() @selector("borderWidth"); 7385 void borderWidth(CGFloat value) @selector("setBorderWidth:"); 7386 7387 CGColorRef borderColor() @selector("borderColor"); 7388 void borderColor(CGColorRef) @selector("setBorderColor:"); 7389 } 7390 7391 7392 extern class NSViewController : NSObject { 7393 NSView view() @selector("view"); 7394 void view(NSView view) @selector("setView:"); 7395 } 7396 7397 enum NSBackingStoreType : size_t { 7398 retained = 0, 7399 nonretained = 1, 7400 buffered = 2 7401 } 7402 7403 enum NSStringEncoding : NSUInteger { 7404 NSASCIIStringEncoding = 1, /* 0..127 only */ 7405 NSUTF8StringEncoding = 4, 7406 NSUnicodeStringEncoding = 10, 7407 7408 NSUTF16StringEncoding = NSUnicodeStringEncoding, 7409 NSUTF16BigEndianStringEncoding = 0x90000100, 7410 NSUTF16LittleEndianStringEncoding = 0x94000100, 7411 NSUTF32StringEncoding = 0x8c000100, 7412 NSUTF32BigEndianStringEncoding = 0x98000100, 7413 NSUTF32LittleEndianStringEncoding = 0x9c000100 7414 } 7415 7416 7417 struct CGColor; 7418 alias CGColorRef = CGColor*; 7419 7420 // note on the watch os it is float, not double 7421 alias CGFloat = double; 7422 7423 struct NSPoint { 7424 CGFloat x; 7425 CGFloat y; 7426 } 7427 7428 struct NSSize { 7429 CGFloat width; 7430 CGFloat height; 7431 } 7432 7433 struct NSRect { 7434 NSPoint origin; 7435 NSSize size; 7436 } 7437 7438 alias NSPoint CGPoint; 7439 alias NSSize CGSize; 7440 alias NSRect CGRect; 7441 7442 pragma(inline, true) NSPoint NSMakePoint(CGFloat x, CGFloat y) { 7443 NSPoint p; 7444 p.x = x; 7445 p.y = y; 7446 return p; 7447 } 7448 7449 pragma(inline, true) NSSize NSMakeSize(CGFloat w, CGFloat h) { 7450 NSSize s; 7451 s.width = w; 7452 s.height = h; 7453 return s; 7454 } 7455 7456 pragma(inline, true) NSRect NSMakeRect(CGFloat x, CGFloat y, CGFloat w, CGFloat h) { 7457 NSRect r; 7458 r.origin.x = x; 7459 r.origin.y = y; 7460 r.size.width = w; 7461 r.size.height = h; 7462 return r; 7463 } 7464 7465 7466 } 7467 7468 // helper raii refcount object 7469 static if(UseCocoa) 7470 struct MacString { 7471 union { 7472 // must be wrapped cuz of bug in dmd 7473 // referencing an init symbol when it should 7474 // just be null. but the union makes it work 7475 NSString s; 7476 } 7477 7478 // FIXME: if a string literal it would be kinda nice to use 7479 // the other function. but meh 7480 7481 this(scope const char[] str) { 7482 this.s = NSString.alloc.initWithBytes( 7483 cast(const(ubyte)*) str.ptr, 7484 str.length, 7485 NSStringEncoding.NSUTF8StringEncoding 7486 ); 7487 } 7488 7489 NSString borrow() { 7490 return s; 7491 } 7492 7493 this(this) { 7494 if(s !is null) 7495 s.retain(); 7496 } 7497 7498 ~this() { 7499 if(s !is null) { 7500 s.release(); 7501 s = null; 7502 } 7503 } 7504 } 7505 7506 extern(C) void NSLog(NSString, ...); 7507 extern(C) SEL sel_registerName(const(char)* str); 7508 7509 extern (Objective-C) __gshared NSApplication NSApp_; 7510 7511 NSApplication NSApp() { 7512 if(NSApp_ is null) 7513 NSApp_ = NSApplication.shared_; 7514 return NSApp_; 7515 } 7516 7517 // hacks to work around compiler bug 7518 extern(C) __gshared void* _D4arsd4core17NSGraphicsContext7__ClassZ = null; 7519 extern(C) __gshared void* _D4arsd4core6NSView7__ClassZ = null; 7520 extern(C) __gshared void* _D4arsd4core8NSWindow7__ClassZ = null; 7521 }