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