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