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