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