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