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 Intended to be Supported OSes: 31 * Windows (at least Vista, MAYBE XP) 32 * Linux 33 * FreeBSD 14 (maybe 13 too) 34 * Mac OS 35 36 Eventually also: 37 * ios 38 * OpenBSD 39 * Android 40 * maybe apple watch os? 41 +/ 42 43 44 static if(__traits(compiles, () { import core.interpolation; })) { 45 import core.interpolation; 46 47 alias InterpolationHeader = core.interpolation.InterpolationHeader; 48 alias InterpolationFooter = core.interpolation.InterpolationFooter; 49 alias InterpolatedLiteral = core.interpolation.InterpolatedLiteral; 50 alias InterpolatedExpression = core.interpolation.InterpolatedExpression; 51 } else { 52 // polyfill for old versions 53 struct InterpolationHeader {} 54 struct InterpolationFooter {} 55 struct InterpolatedLiteral(string literal) {} 56 struct InterpolatedExpression(string code) {} 57 } 58 59 static if(!__traits(hasMember, object, "SynchronizableObject")) { 60 alias SynchronizableObject = Object; 61 mixin template EnableSynchronization() {} 62 } else { 63 alias SynchronizableObject = object.SynchronizableObject; 64 alias EnableSynchronization = Object.EnableSynchronization; 65 } 66 67 // the old char.inits if you need them 68 enum char char_invalid = '\xFF'; 69 enum wchar wchar_invalid = '\uFFFF'; 70 enum dchar dchar_invalid = '\U0000FFFF'; 71 72 // arsd core is now default but you can opt out for a lil while 73 version(no_arsd_core) { 74 75 } else { 76 version=use_arsd_core; 77 } 78 79 version(use_arsd_core) 80 enum use_arsd_core = true; 81 else 82 enum use_arsd_core = false; 83 84 import core.attribute; 85 static if(__traits(hasMember, core.attribute, "implicit")) 86 alias implicit = core.attribute.implicit; 87 else 88 enum implicit; 89 90 static if(__traits(hasMember, core.attribute, "standalone")) 91 alias standalone = core.attribute.standalone; 92 else 93 enum standalone; 94 95 96 97 // FIXME: add callbacks on file open for tracing dependencies dynamically 98 99 // see for useful info: https://devblogs.microsoft.com/dotnet/how-async-await-really-works/ 100 101 // see: https://wiki.openssl.org/index.php/Simple_TLS_Server 102 103 // see: When you only want to track changes on a file or directory, be sure to open it using the O_EVTONLY flag. 104 105 ///ArsdUseCustomRuntime is used since other derived work from WebAssembly may be used and thus specified in the CLI 106 version(Emscripten) { 107 version = EmptyEventLoop; 108 version = EmptyCoreEvent; 109 version = HasTimer; 110 } else version(WebAssembly) version = ArsdUseCustomRuntime; 111 else 112 113 // note that kqueue might run an i/o loop on mac, ios, etc. but then NSApp.run on the io thread 114 // but on bsd, you want the kqueue loop in i/o too.... 115 116 version(ArsdUseCustomRuntime) 117 { 118 version = UseStdioWriteln; 119 } 120 else 121 { 122 version(ArsdNoCocoa) { 123 } else { 124 version(D_OpenD) { 125 version(OSX) 126 version=OSXCocoa; 127 version(iOS) 128 version=OSXCocoa; 129 } else version(DigitalMars) { 130 version(OSX) 131 version=OSXCocoa; 132 version(iOS) 133 version=OSXCocoa; 134 } else version(LDC) { 135 version(OSX) 136 version=OSXCocoa; 137 version(iOS) 138 version=OSXCocoa; 139 } 140 } 141 142 version = HasFile; 143 version = HasSocket; 144 version = HasThread; 145 import core.stdc.errno; 146 147 version(Windows) 148 version = HasTimer; 149 version(linux) 150 version = HasTimer; 151 version(OSX) 152 version = HasTimer; 153 } 154 155 version(HasThread) 156 { 157 import core.thread; 158 import core..volatile; 159 import core.atomic; 160 } 161 else 162 { 163 // polyfill for missing core.time 164 /* 165 struct Duration { 166 static Duration max() { return Duration(); } 167 } 168 struct MonoTime {} 169 */ 170 } 171 172 import core.time; 173 174 version(OSXCocoa) { 175 version(ArsdNoCocoa) 176 enum bool UseCocoa = false; 177 else { 178 version=UseCocoa; 179 enum bool UseCocoa = true; 180 } 181 } else 182 enum bool UseCocoa = false; 183 184 import core.attribute; 185 static if(!__traits(hasMember, core.attribute, "mustuse")) 186 enum mustuse; 187 188 // FIXME: add an arena allocator? can do task local destruction maybe. 189 190 // the three implementations are windows, epoll, and kqueue 191 192 version(Emscripten) { 193 import core.stdc.errno; 194 import core.atomic; 195 import core..volatile; 196 197 } else version(Windows) { 198 version=Arsd_core_windows; 199 200 // import core.sys.windows.windows; 201 import core.sys.windows.winbase; 202 import core.sys.windows.windef; 203 import core.sys.windows.winnls; 204 import core.sys.windows.winuser; 205 import core.sys.windows.winsock2; 206 207 pragma(lib, "user32"); 208 pragma(lib, "ws2_32"); 209 } else version(linux) { 210 version=Arsd_core_epoll; 211 212 static if(__VERSION__ >= 2098) { 213 version=Arsd_core_has_cloexec; 214 } 215 } else version(FreeBSD) { 216 version=Arsd_core_kqueue; 217 218 import core.sys.freebsd.sys.event; 219 220 // the version in druntime doesn't have the default arg making it a pain to use when the freebsd 221 // version adds a new field 222 extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args = kevent_t.tupleof.init) 223 { 224 *kevp = kevent_t(args); 225 } 226 } else version(DragonFlyBSD) { 227 // NOT ACTUALLY TESTED 228 version=Arsd_core_kqueue; 229 230 import core.sys.dragonflybsd.sys.event; 231 } else version(NetBSD) { 232 // NOT ACTUALLY TESTED 233 version=Arsd_core_kqueue; 234 235 import core.sys.netbsd.sys.event; 236 } else version(OpenBSD) { 237 version=Arsd_core_kqueue; 238 239 // THIS FILE DOESN'T ACTUALLY EXIST, WE NEED TO MAKE IT 240 import core.sys.openbsd.sys.event; 241 } else version(OSX) { 242 version(ArsdNoCocoa) { 243 version=Arsd_core_kqueue; 244 } else { 245 version=Arsd_core_dispatch; 246 } 247 248 import core.sys.darwin.sys.event; 249 } else version(iOS) { 250 version=Arsd_core_dispatch; 251 252 import core.sys.darwin.sys.event; 253 } 254 255 // FIXME: pragma(linkerDirective, "-framework", "Cocoa") works in ldc 256 static if(UseCocoa) 257 enum CocoaAvailable = true; 258 else 259 enum CocoaAvailable = false; 260 261 version(D_OpenD) { 262 static if(UseCocoa) { 263 pragma(linkerDirective, "-framework", "Cocoa"); 264 pragma(linkerDirective, "-framework", "QuartzCore"); 265 } 266 } else { 267 static if(UseCocoa) 268 version(LDC) { 269 pragma(linkerDirective, "-framework", "Cocoa"); 270 pragma(linkerDirective, "-framework", "QuartzCore"); 271 } 272 } 273 274 version(Posix) { 275 import core.sys.posix.signal; 276 import core.sys.posix.unistd; 277 278 version(Emscripten) {} else { 279 import core.sys.posix.sys.un; 280 import core.sys.posix.sys.socket; 281 import core.sys.posix.netinet.in_; 282 } 283 } 284 285 // FIXME: the exceptions should actually give some explanatory text too (at least sometimes) 286 287 /+ 288 ========================= 289 GENERAL UTILITY FUNCTIONS 290 ========================= 291 +/ 292 293 /++ 294 Casts value `v` to type `T`. 295 296 $(TIP 297 This is a helper function for readability purposes. 298 The idea is to make type-casting as accessible as `to()` from `std.conv`. 299 ) 300 301 --- 302 int i = cast(int)(foo * bar); 303 int i = castTo!int(foo * bar); 304 305 int j = cast(int) round(floatValue); 306 int j = round(floatValue).castTo!int; 307 308 int k = cast(int) floatValue + foobar; 309 int k = floatValue.castTo!int + foobar; 310 311 auto m = Point( 312 cast(int) calc(a.x, b.x), 313 cast(int) calc(a.y, b.y), 314 ); 315 auto m = Point( 316 calc(a.x, b.x).castTo!int, 317 calc(a.y, b.y).castTo!int, 318 ); 319 --- 320 321 History: 322 Added on April 24, 2024. 323 Renamed from `typeCast` to `castTo` on May 24, 2024. 324 +/ 325 auto ref T castTo(T, S)(auto ref S v) { 326 return cast(T) v; 327 } 328 329 /// 330 alias typeCast = castTo; 331 332 /++ 333 Treats the memory of one variable as if it is the type of another variable. 334 335 History: 336 Added January 20, 2025 337 +/ 338 ref T reinterpretCast(T, V)(return ref V value) @system { 339 return *cast(T*)& value; 340 } 341 342 /++ 343 Determines whether `needle` is a slice of `haystack`. 344 345 History: 346 Added on February 11, 2025. 347 +/ 348 bool isSliceOf(T1, T2)(scope const(T1)[] needle, scope const(T2)[] haystack) @trusted pure nothrow @nogc { 349 return ( 350 needle.ptr >= haystack.ptr 351 && ((needle.ptr + needle.length) <= (haystack.ptr + haystack.length)) 352 ); 353 } 354 355 /// 356 @safe unittest { 357 string s0 = "01234"; 358 const(char)[] s1 = s0[1 .. $]; 359 const(void)[] s2 = s1.castTo!(const(void)[]); 360 string s3 = s1.idup; 361 362 assert( s0.isSliceOf(s0)); 363 assert( s1.isSliceOf(s0)); 364 assert( s2.isSliceOf(s0)); 365 assert(!s3.isSliceOf(s0)); 366 367 assert(!s0.isSliceOf(s1)); 368 assert( s1.isSliceOf(s1)); 369 assert( s2.isSliceOf(s1)); 370 assert(!s3.isSliceOf(s1)); 371 372 assert(!s0.isSliceOf(s2)); 373 assert( s1.isSliceOf(s2)); 374 assert( s2.isSliceOf(s2)); 375 assert(!s3.isSliceOf(s2)); 376 377 assert(!s0.isSliceOf(s3)); 378 assert(!s1.isSliceOf(s3)); 379 assert(!s2.isSliceOf(s3)); 380 assert( s3.isSliceOf(s3)); 381 382 assert(s1.length == 4); 383 assert(s1[0 .. 0].isSliceOf(s1)); 384 assert(s1[0 .. 1].isSliceOf(s1)); 385 assert(s1[1 .. 2].isSliceOf(s1)); 386 assert(s1[1 .. 3].isSliceOf(s1)); 387 assert(s1[1 .. $].isSliceOf(s1)); 388 assert(s1[$ .. $].isSliceOf(s1)); 389 } 390 391 /++ 392 Does math as a 64 bit number, but saturates at int.min and int.max when converting back to a 32 bit int. 393 394 History: 395 Added January 1, 2025 396 +/ 397 alias NonOverflowingInt = NonOverflowingIntBase!(int.min, int.max); 398 399 /// ditto 400 alias NonOverflowingUint = NonOverflowingIntBase!(0, int.max); 401 402 /// ditto 403 struct NonOverflowingIntBase(int min, int max) { 404 this(long v) { 405 this.value = v; 406 } 407 408 private long value; 409 410 NonOverflowingInt opBinary(string op)(long rhs) { 411 return NonOverflowingInt(mixin("this.value", op, "rhs")); 412 } 413 NonOverflowingInt opBinary(string op)(NonOverflowingInt rhs) { 414 return this.opBinary!op(rhs.value); 415 } 416 NonOverflowingInt opUnary(string op)() { 417 return NonOverflowingInt(mixin(op, "this.value")); 418 } 419 NonOverflowingInt opOpAssign(string op)(long rhs) { 420 return this = this.opBinary!(op)(rhs); 421 } 422 NonOverflowingInt opOpAssign(string op)(NonOverflowingInt rhs) { 423 return this = this.opBinary!(op)(rhs.value); 424 } 425 426 int getValue() const { 427 if(value < min) 428 return min; 429 else if(value > max) 430 return max; 431 return cast(int) value; 432 } 433 434 alias getValue this; 435 } 436 437 unittest { 438 assert(-5.NonOverflowingInt - int.max == int.min); 439 assert(-5.NonOverflowingInt + 5 == 0); 440 441 assert(NonOverflowingInt(5) + int.max - 5 == int.max); 442 assert(NonOverflowingInt(5) + int.max - int.max - 5 == 0); // it truncates at the end of the op chain, not at intermediates 443 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 444 } 445 446 // enum stringz : const(char)* { init = null } 447 448 /++ 449 A wrapper around a `const(char)*` to indicate that it is a zero-terminated C string. 450 +/ 451 struct stringz { 452 private const(char)* raw; 453 454 /++ 455 Wraps the given pointer in the struct. Note that it retains a copy of the pointer. 456 +/ 457 this(const(char)* raw) { 458 this.raw = raw; 459 } 460 461 /++ 462 Returns the original raw pointer back out. 463 +/ 464 const(char)* ptr() const { 465 return raw; 466 } 467 468 /++ 469 Borrows a slice of the pointer up to (but not including) the zero terminator. 470 +/ 471 const(char)[] borrow() const @system { 472 if(raw is null) 473 return null; 474 475 const(char)* p = raw; 476 int length; 477 while(*p++) length++; 478 479 return raw[0 .. length]; 480 } 481 } 482 483 /+ 484 /++ 485 A runtime tagged union, aka a sumtype. 486 487 History: 488 Added February 15, 2025 489 +/ 490 struct Union(T...) { 491 private uint contains_; 492 private union { 493 private T payload; 494 } 495 496 static foreach(index, type; T) 497 @implicit public this(type t) { 498 contains_ = index; 499 payload[index] = t; 500 } 501 502 bool contains(Part)() const { 503 static assert(indexFor!Part != -1); 504 return contains_ == indexFor!Part; 505 } 506 507 inout(Part) get(Part)() inout { 508 if(!contains!Part) { 509 throw new ArsdException!"Dynamic type mismatch"(indexFor!Part, contains_); 510 } 511 return payload[indexFor!Part]; 512 } 513 514 private int indexFor(Part)() { 515 foreach(idx, thing; T) 516 static if(is(T == Part)) 517 return idx; 518 return -1; 519 } 520 } 521 +/ 522 523 /+ 524 DateTime 525 year: 16 bits (-32k to +32k) 526 month: 4 bits 527 day: 5 bits 528 529 hour: 5 bits 530 minute: 6 bits 531 second: 6 bits 532 533 total: 25 bits + 17 bits = 42 bits 534 535 fractional seconds: 10 bits (about milliseconds) 536 537 accuracy flags: date_valid | time_valid = 2 bits 538 539 54 bits used, 8 bits remain. reserve 1 for signed. 540 541 tz offset in 15 minute intervals = 96 slots... can fit in 7 remaining bits... 542 543 would need 11 bits for minute-precise dt offset but meh. would need 10 bits for referring back to tz database (and that's iffy to key, better to use a string tbh) 544 +/ 545 546 /++ 547 A packed date/time/datetime representation added for use with LimitedVariant. 548 549 You should probably not use this much directly, it is mostly an internal storage representation. 550 +/ 551 struct PackedDateTime { 552 private ulong packedData; 553 554 string toString() const { 555 char[64] buffer; 556 size_t pos; 557 558 if(hasDate) { 559 pos += intToString(year, buffer[pos .. $], IntToStringArgs().withPadding(4)).length; 560 buffer[pos++] = '-'; 561 pos += intToString(month, buffer[pos .. $], IntToStringArgs().withPadding(2)).length; 562 buffer[pos++] = '-'; 563 pos += intToString(day, buffer[pos .. $], IntToStringArgs().withPadding(2)).length; 564 } 565 566 if(hasTime) { 567 if(pos) 568 buffer[pos++] = 'T'; 569 570 pos += intToString(hours, buffer[pos .. $], IntToStringArgs().withPadding(2)).length; 571 buffer[pos++] = ':'; 572 pos += intToString(minutes, buffer[pos .. $], IntToStringArgs().withPadding(2)).length; 573 buffer[pos++] = ':'; 574 pos += intToString(seconds, buffer[pos .. $], IntToStringArgs().withPadding(2)).length; 575 if(fractionalSeconds) { 576 buffer[pos++] = '.'; 577 pos += intToString(fractionalSeconds, buffer[pos .. $], IntToStringArgs().withPadding(4)).length; 578 } 579 } 580 581 return buffer[0 .. pos].idup; 582 } 583 584 /++ 585 Construction helpers 586 +/ 587 static PackedDateTime withDate(int year, int month, int day) { 588 PackedDateTime p; 589 p.setDate(year, month, day); 590 return p; 591 } 592 /// ditto 593 static PackedDateTime withTime(int hours, int minutes, int seconds, int fractionalSeconds = 0) { 594 PackedDateTime p; 595 p.setTime(hours, minutes, seconds, fractionalSeconds); 596 return p; 597 } 598 /// ditto 599 static PackedDateTime withDateAndTime(int year, int month, int day = 1, int hours = 0, int minutes = 0, int seconds = 0, int fractionalSeconds = 0) { 600 PackedDateTime p; 601 p.setDate(year, month, day); 602 p.setTime(hours, minutes, seconds, fractionalSeconds); 603 return p; 604 } 605 /// ditto 606 static PackedDateTime lastDayOfMonth(int year, int month) { 607 PackedDateTime p; 608 p.setDate(year, month, daysInMonth(year, month)); 609 return p; 610 } 611 /++ +/ 612 static bool isLeapYear(int year) { 613 return 614 (year % 4) == 0 615 && 616 ( 617 ((year % 100) != 0) 618 || 619 ((year % 400) == 0) 620 ) 621 ; 622 } 623 unittest { 624 assert(isLeapYear(2024)); 625 assert(!isLeapYear(2023)); 626 assert(!isLeapYear(2025)); 627 assert(isLeapYear(2000)); 628 assert(!isLeapYear(1900)); 629 } 630 static immutable ubyte[12] daysInMonthTable = [ 631 31, 28, 31, 30, 31, 30, 632 31, 31, 30, 31, 30, 31 633 ]; 634 635 static int daysInMonth(int year, int month) { 636 assert(month >= 1 && month <= 12); 637 if(month == 2) 638 return isLeapYear(year) ? 29 : 28; 639 else 640 return daysInMonthTable[month - 1]; 641 } 642 unittest { 643 assert(daysInMonth(2025, 12) == 31); 644 assert(daysInMonth(2025, 2) == 28); 645 assert(daysInMonth(2024, 2) == 29); 646 } 647 static int daysInYear(int year) { 648 return isLeapYear(year) ? 366 : 365; 649 } 650 651 /++ 652 Sets the whole date and time portions in one function call. 653 654 History: 655 Added December 13, 2025 656 +/ 657 void setTime(int hours, int minutes, int seconds, int fractionalSeconds = 0) { 658 this.hours = hours; 659 this.minutes = minutes; 660 this.seconds = seconds; 661 this.fractionalSeconds = fractionalSeconds; 662 this.hasTime = true; 663 } 664 665 /// ditto 666 void setDate(int year, int month, int day) { 667 this.year = year; 668 this.month = month; 669 this.day = day; 670 this.hasDate = true; 671 } 672 673 /// ditto 674 void clearTime() { 675 this.hours = 0; 676 this.minutes = 0; 677 this.seconds = 0; 678 this.fractionalSeconds = 0; 679 this.hasTime = false; 680 } 681 682 /// ditto 683 void clearDate() { 684 this.year = 0; 685 this.month = 0; 686 this.day = 0; 687 this.hasDate = false; 688 } 689 690 /++ 691 +/ 692 int fractionalSeconds() const { return getFromMask(00, 10); } 693 /// ditto 694 void fractionalSeconds(int a) { setWithMask(a, 00, 10); } 695 696 /// ditto 697 int seconds() const { return getFromMask(10, 6); } 698 /// ditto 699 void seconds(int a) { setWithMask(a, 10, 6); } 700 /// ditto 701 int minutes() const { return getFromMask(16, 6); } 702 /// ditto 703 void minutes(int a) { setWithMask(a, 16, 6); } 704 /// ditto 705 int hours() const { return getFromMask(22, 5); } 706 /// ditto 707 void hours(int a) { setWithMask(a, 22, 5); } 708 709 /// ditto 710 int day() const { return getFromMask(27, 5); } 711 /// ditto 712 void day(int a) { setWithMask(a, 27, 5); } 713 /// ditto 714 int month() const { return getFromMask(32, 4); } 715 /// ditto 716 void month(int a) { setWithMask(a, 32, 4); } 717 /// ditto 718 int year() const { return getFromMask(36, 16); } 719 /// ditto 720 void year(int a) { setWithMask(a, 36, 16); } 721 722 /// ditto 723 bool hasTime() const { return cast(bool) getFromMask(52, 1); } 724 /// ditto 725 void hasTime(bool a) { setWithMask(a, 52, 1); } 726 /// ditto 727 bool hasDate() const { return cast(bool) getFromMask(53, 1); } 728 /// ditto 729 void hasDate(bool a) { setWithMask(a, 53, 1); } 730 731 private void setWithMask(int a, int bitOffset, int bitCount) { 732 auto mask = (1UL << bitCount) - 1; 733 734 packedData &= ~(mask << bitOffset); 735 packedData |= (a & mask) << bitOffset; 736 } 737 738 private int getFromMask(int bitOffset, int bitCount) const { 739 ulong packedData = this.packedData; 740 packedData >>= bitOffset; 741 742 ulong mask = (1UL << bitCount) - 1; 743 744 return cast(int) (packedData & mask); 745 } 746 747 /++ 748 Returns the day of week for the date portion. 749 750 Throws AssertError if used when [hasDate] is false. 751 752 Returns: 753 0 == Sunday, 6 == Saturday 754 755 History: 756 Added December 13, 2025 757 +/ 758 int dayOfWeek() const { 759 assert(hasDate); 760 auto y = year; 761 auto m = month; 762 if(m == 1 || m == 2) { 763 y--; 764 m += 12; 765 } 766 return ( 767 day + 768 (13 * (m+1) / 5) + 769 (y % 100) + 770 (y % 100) / 4 + 771 (y / 100) / 4 - 772 2 * (y / 100) 773 ) % 7; 774 } 775 776 long opCmp(PackedDateTime rhs) const { 777 if(this.hasDate == rhs.hasDate && this.hasTime == rhs.hasTime) 778 return cast(long) this.packedData - cast(long) rhs.packedData; 779 if(this.hasDate && rhs.hasDate) { 780 PackedDateTime c1 = this; 781 c1.clearTime(); 782 rhs.clearTime(); 783 return c1.opCmp(rhs); 784 } 785 // if one of them is just time, no date, we can't compare 786 // but as long as there's two date components we can compare them. 787 assert(0, "invalid comparison, one is a date, other is a time"); 788 } 789 } 790 791 unittest { 792 PackedDateTime dt; 793 dt.hours = 14; 794 dt.minutes = 30; 795 dt.seconds = 25; 796 dt.hasTime = true; 797 798 assert(dt.toString() == "14:30:25", dt.toString()); 799 800 dt.hasTime = false; 801 dt.year = 2024; 802 dt.month = 5; 803 dt.day = 31; 804 dt.hasDate = true; 805 806 assert(dt.toString() == "2024-05-31", dt.toString()); 807 dt.hasTime = true; 808 assert(dt.toString() == "2024-05-31T14:30:25", dt.toString()); 809 810 assert(dt.dayOfWeek == 6); 811 } 812 813 unittest { 814 PackedDateTime a; 815 PackedDateTime b; 816 a.setDate(2025, 01, 01); 817 b.setDate(2024, 12, 31); 818 assert(a > b); 819 } 820 821 /++ 822 A `PackedInterval` can be thought of as the difference between [PackedDateTime]s, similarly to how a [Duration] is a difference between [MonoTime]s or [SimplifiedUtcTimestamp]s. 823 824 825 The key speciality is in how it treats months and days separately. Months are not a consistent length, and neither are days when you consider daylight saving time. This thing assumes that if you add those, the month/day number will always increase, just the exact details since then might be truncated. (E.g., January 31st + 1 month = February 28/29 depending on leap year). If you multiply, the parts are done individually, so January 31st + 1 month * 2 = March 31st, despite + 1 month truncating to the shorter day in February. 826 827 Internally, this stores months and days as 16 bit signed `short`s each, then the milliseconds is stored as a 32 bit signed `int`. It applies by first adding months, truncating days as needed, then adding days, then adding milliseconds. 828 829 If you iterate over intervals, be careful not to allow month truncation to change the result. (Jan 31st + 1 month) + 1 month will not actually give the same result as Jan 31st + 2 months. You want to add to the interval, then apply to the original date again, not to some accumulated date. 830 831 History: 832 Added December 13, 2025 833 +/ 834 struct PackedInterval { 835 private ulong packedData; 836 837 this(int months, int days = 0, int milliseconds = 0) { 838 this.months = months; 839 this.days = days; 840 this.milliseconds = milliseconds; 841 } 842 843 /++ 844 Getters and setters for the components 845 +/ 846 short months() const { 847 return cast(short)((packedData >> 48) & 0xffff); 848 } 849 850 /// ditto 851 short days() const { 852 return cast(short)((packedData >> 32) & 0xffff); 853 } 854 855 /// ditto 856 int milliseconds() const { 857 return cast(int)(packedData & 0xffff_ffff); 858 } 859 860 /// ditto 861 void months(int v) { 862 short d = cast(short) v; 863 ulong s = d; 864 packedData &= ~(0xffffUL << 48); 865 packedData |= s << 48; 866 } 867 868 /// ditto 869 void days(int v) { 870 short d = cast(short) v; 871 ulong s = d; 872 packedData &= ~(0xffffUL << 32); 873 packedData |= s << 32; 874 } 875 876 /// ditto 877 void milliseconds(int v) { 878 packedData &= 0xffffffff_00000000UL; 879 packedData |= cast(ulong) v; 880 } 881 882 PackedInterval opBinary(string op : "*")(int iterations) const { 883 return PackedInterval(this.months * iterations, this.days * iterations, this.milliseconds * iterations); 884 } 885 } 886 887 unittest { 888 PackedInterval pi = PackedInterval(1); 889 assert(pi.months == 1); 890 assert(pi.days == 0); 891 assert(pi.milliseconds == 0); 892 } 893 894 /++ 895 Basically a Phobos SysTime but standing alone as a simple 64 bit integer (but wrapped) for compatibility with LimitedVariant. 896 +/ 897 struct SimplifiedUtcTimestamp { 898 long timestamp; 899 900 this(long hnsecTimestamp) { 901 this.timestamp = hnsecTimestamp; 902 } 903 904 // this(PackedDateTime pdt) 905 906 string toString() const { 907 import core.stdc.time; 908 char[128] buffer; 909 auto ut = toUnixTime(); 910 tm* t = gmtime(&ut); 911 if(t is null) 912 return "null time"; 913 914 return buffer[0 .. strftime(buffer.ptr, buffer.length, "%Y-%m-%dT%H:%M:%SZ", t)].idup; 915 } 916 917 version(Windows) 918 alias time_t = int; 919 920 static SimplifiedUtcTimestamp fromUnixTime(time_t t) { 921 return SimplifiedUtcTimestamp(621_355_968_000_000_000L + t * 1_000_000_000L / 100); 922 } 923 924 /++ 925 History: 926 Added November 22, 2025 927 +/ 928 static SimplifiedUtcTimestamp now() { 929 import core.stdc.time; 930 return SimplifiedUtcTimestamp.fromUnixTime(time(null)); 931 } 932 933 time_t toUnixTime() const { 934 return cast(time_t) ((timestamp - 621_355_968_000_000_000L) / 1_000_000_0); // hnsec = 7 digits 935 } 936 937 long stdTime() const { 938 return timestamp; 939 } 940 941 SimplifiedUtcTimestamp opBinary(string op : "+")(Duration d) const { 942 return SimplifiedUtcTimestamp(this.timestamp + d.total!"hnsecs"); 943 } 944 } 945 946 unittest { 947 SimplifiedUtcTimestamp sut = SimplifiedUtcTimestamp.fromUnixTime(86_400); 948 assert(sut.toString() == "1970-01-02T00:00:00Z"); 949 } 950 951 /++ 952 A little builder pattern helper that is meant for use by other library code. 953 954 History: 955 Added October 31, 2025 956 +/ 957 struct AdHocBuiltStruct(string tag, string[] names = [], T...) { 958 static assert(names.length == T.length); 959 960 T values; 961 962 auto opDispatch(string name, Arg)(Arg value) { 963 return AdHocBuiltStruct!(tag, names ~ name, T, Arg)(values, value); 964 } 965 } 966 967 unittest { 968 AdHocBuiltStruct!"tag"() 969 .id(5) 970 .name("five") 971 ; 972 } 973 974 /++ 975 Represents a generic raw element to be embedded in an interpolated sequence. 976 977 Use with caution, its exact meaning is dependent on the specific function being called, but it generally is meant to disable encoding protections the function normally provides. 978 979 History: 980 Added October 31, 2025 981 +/ 982 struct iraw { 983 string s; 984 985 @system this(string s) { 986 this.s = s; 987 } 988 } 989 990 /++ 991 Counts the number of bits set to `1` in a value, using intrinsics when available. 992 993 History: 994 Added December 15, 2025 995 +/ 996 int countOfBitsSet(ulong v) { 997 version(LDC) { 998 import ldc.intrinsics; 999 return cast(int) llvm_ctpop(v); 1000 } else { 1001 // kerninghan's algorithm 1002 int count = 0; 1003 while(v) { 1004 v &= v - 1; 1005 count++; 1006 } 1007 return count; 1008 } 1009 } 1010 1011 unittest { 1012 assert(countOfBitsSet(0) == 0); 1013 assert(countOfBitsSet(ulong.max) == 64); 1014 assert(countOfBitsSet(0x0f0f) == 8); 1015 assert(countOfBitsSet(0x0f0f2) == 9); 1016 } 1017 1018 /++ 1019 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. 1020 1021 Note that empty strings and null values are indistinguishable unless you explicitly slice the end of some other existing string! 1022 +/ 1023 /+ 1024 ALL OF THESE ARE SUBJECT TO CHANGE 1025 1026 * if length and ptr are both 0, it is null 1027 * if ptr == 1, length is an integer 1028 * if ptr == 2, length is an unsigned integer (suggest printing in hex) 1029 * if ptr == 3, length is a combination of flags (suggest printing in binary) 1030 * if ptr == 4, length is a unix permission thing (suggest printing in octal) 1031 * if ptr == 5, length is a double float 1032 * if ptr == 6, length is an Object ref (reinterpret casted to void*) 1033 1034 * if ptr == 7, length is a ticks count (from MonoTime) 1035 * if ptr == 8, length is a utc timestamp (hnsecs) 1036 * if ptr == 9, length is a duration (signed hnsecs) 1037 * 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) 1038 * if ptr == 11, length is a decimal 1039 1040 * if ptr == 12, length is a bool (redundant to int?) 1041 13, 14 reserved. maybe char? 1042 1043 * if ptr == 15, length must be 0. this holds an empty, non-null, SSO string. 1044 * if ptr >= 16 && < 24, length is reinterpret-casted a small string of length of (ptr & 0x7) + 1 1045 1046 * if length == size_t.max, ptr is interpreted as a stringz 1047 * 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. 1048 1049 All other ptr values are reserved for future expansion. 1050 1051 It basically can store: 1052 null 1053 type details = must be 0 1054 int (actually long) 1055 type details = formatting hints 1056 float (actually double) 1057 type details = formatting hints 1058 dchar (actually enum - upper half is the type tag, lower half is the member tag) 1059 type details = ??? 1060 decimal 1061 type details = precision specifier 1062 object 1063 type details = ??? 1064 timestamp 1065 type details: ticks, utc timestamp, relative duration 1066 1067 sso 1068 stringz 1069 1070 or it is bytes or a string; a normal D array (just bytes has a high bit set on length). 1071 1072 But there are subtypes of some of those; ints can just have formatting hints attached. 1073 Could reserve 0-7 as low level type flag (null, int, float, pointer, object) 1074 15-24 still can be the sso thing 1075 1076 We have 10 bits really. 1077 1078 00000 00000 1079 ????? OOLLL 1080 1081 The ????? are type details bits. 1082 1083 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) 1084 ...actually it can just be a dollars * 10000 + cents * 100. 1085 1086 +/ 1087 struct LimitedVariant { 1088 1089 /++ 1090 1091 +/ 1092 enum Contains { 1093 null_, 1094 intDecimal, 1095 intHex, 1096 intBinary, 1097 intOctal, 1098 double_, 1099 object, 1100 1101 monoTime, 1102 utcTimestamp, 1103 duration, 1104 dateTime, 1105 decimal, 1106 1107 // FIXME interval like postgres? e.g. 30 days, 2 months. distinct from Duration, which is a difference of monoTimes or utcTimestamps, interval is more like a difference of PackedDateTime. 1108 // FIXME boolean? char? specializations of float for various precisions... 1109 1110 // could do enums by way of a pointer but kinda iffy 1111 1112 // maybe some kind of prefixed string too for stuff like xml and json or enums etc. 1113 1114 // fyi can also use stringzs or length-prefixed string pointers 1115 emptySso, 1116 stringSso, 1117 stringz, 1118 string, 1119 bytes, 1120 1121 invalid, 1122 } 1123 1124 /++ 1125 Each datum stored in the LimitedVariant has a tag associated with it. 1126 1127 Each tag belongs to one or more data families. 1128 +/ 1129 Contains contains() const { 1130 auto tag = cast(size_t) ptr; 1131 if(ptr is null && length is null) 1132 return Contains.null_; 1133 else switch(tag) { 1134 case 1: return Contains.intDecimal; 1135 case 2: return Contains.intHex; 1136 case 3: return Contains.intBinary; 1137 case 4: return Contains.intOctal; 1138 case 5: return Contains.double_; 1139 case 6: return Contains.object; 1140 1141 case 7: return Contains.monoTime; 1142 case 8: return Contains.utcTimestamp; 1143 case 9: return Contains.duration; 1144 case 10: return Contains.dateTime; 1145 case 11: return Contains.decimal; 1146 1147 case 15: return length is null ? Contains.emptySso : Contains.invalid; 1148 default: 1149 if(tag >= 16 && tag < 24) { 1150 return Contains.stringSso; 1151 } else if(tag >= 1024) { 1152 if(cast(size_t) length == size_t.max) 1153 return Contains.stringz; 1154 else 1155 return isHighBitSet ? Contains.bytes : Contains..string; 1156 } else { 1157 return isHighBitSet ? Contains.bytes : Contains.invalid; 1158 } 1159 } 1160 } 1161 1162 /// ditto 1163 bool containsNull() const { 1164 return contains() == Contains.null_; 1165 } 1166 1167 /// ditto 1168 bool containsDecimal() const { 1169 return contains() == Contains.decimal; 1170 } 1171 1172 /// ditto 1173 bool containsInt() const { 1174 with(Contains) 1175 switch(contains) { 1176 case intDecimal, intHex, intBinary, intOctal: 1177 return true; 1178 default: 1179 return false; 1180 } 1181 } 1182 1183 // all specializations of int... 1184 1185 /// ditto 1186 bool containsMonoTime() const { 1187 return contains() == Contains.monoTime; 1188 } 1189 /// ditto 1190 bool containsUtcTimestamp() const { 1191 return contains() == Contains.utcTimestamp; 1192 } 1193 /// ditto 1194 bool containsDuration() const { 1195 return contains() == Contains.duration; 1196 } 1197 /// ditto 1198 bool containsDateTime() const { 1199 return contains() == Contains.dateTime; 1200 } 1201 1202 // done int specializations 1203 1204 /// ditto 1205 bool containsString() const { 1206 with(Contains) 1207 switch(contains) { 1208 case null_, emptySso, stringSso, string: 1209 case stringz: 1210 return true; 1211 default: 1212 return false; 1213 } 1214 } 1215 1216 /// ditto 1217 bool containsDouble() const { 1218 with(Contains) 1219 switch(contains) { 1220 case double_: 1221 return true; 1222 default: 1223 return false; 1224 } 1225 } 1226 1227 /// ditto 1228 bool containsBytes() const { 1229 with(Contains) 1230 switch(contains) { 1231 case bytes, null_: 1232 return true; 1233 default: 1234 return false; 1235 } 1236 } 1237 1238 private const(void)* length; 1239 private const(ubyte)* ptr; 1240 1241 private void Throw() const { 1242 throw ArsdException!"LimitedVariant"(cast(size_t) length, cast(size_t) ptr); 1243 } 1244 1245 private bool isHighBitSet() const { 1246 return (cast(size_t) length >> (size_t.sizeof * 8 - 1) & 0x1) != 0; 1247 } 1248 1249 /++ 1250 getString gets a reference to the string stored internally, which may be a temporary. See [toString] to get a normal string representation or whatever is inside. 1251 1252 +/ 1253 const(char)[] getString() const return { 1254 with(Contains) 1255 switch(contains()) { 1256 case null_: 1257 return null; 1258 case emptySso: 1259 return (cast(const(char)*) ptr)[0 .. 0]; // zero length, non-null 1260 case stringSso: 1261 auto len = ((cast(size_t) ptr) & 0x7) + 1; 1262 return (cast(char*) &length)[0 .. len]; 1263 case string: 1264 return (cast(const(char)*) ptr)[0 .. cast(size_t) length]; 1265 case stringz: 1266 return arsd.core.stringz(cast(char*) ptr).borrow; 1267 default: 1268 Throw(); assert(0); 1269 } 1270 } 1271 1272 /// ditto 1273 long getInt() const { 1274 if(containsInt) 1275 return cast(long) length; 1276 else 1277 Throw(); 1278 assert(0); 1279 } 1280 1281 /// ditto 1282 double getDouble() const { 1283 if(containsDouble) { 1284 floathack hack; 1285 hack.e = cast(void*) length; // casting away const 1286 return hack.d; 1287 } else 1288 Throw(); 1289 assert(0); 1290 } 1291 1292 /// ditto 1293 const(ubyte)[] getBytes() const { 1294 with(Contains) 1295 switch(contains()) { 1296 case null_: 1297 return null; 1298 case bytes: 1299 return ptr[0 .. (cast(size_t) length) & ((1UL << (size_t.sizeof * 8 - 1)) - 1)]; 1300 default: 1301 Throw(); assert(0); 1302 } 1303 } 1304 1305 /// ditto 1306 Object getObject() const { 1307 with(Contains) 1308 switch(contains()) { 1309 case null_: 1310 return null; 1311 case object: 1312 return cast(Object) length; // FIXME const correctness sigh 1313 default: 1314 Throw(); assert(0); 1315 } 1316 } 1317 1318 /// ditto 1319 MonoTime getMonoTime() const { 1320 if(containsMonoTime) { 1321 MonoTime time; 1322 __traits(getMember, time, "_ticks") = cast(long) length; 1323 return time; 1324 } else 1325 Throw(); 1326 assert(0); 1327 } 1328 /// ditto 1329 SimplifiedUtcTimestamp getUtcTimestamp() const { 1330 if(containsUtcTimestamp) 1331 return SimplifiedUtcTimestamp(cast(long) length); 1332 else 1333 Throw(); 1334 assert(0); 1335 } 1336 /// ditto 1337 Duration getDuration() const { 1338 if(containsDuration) 1339 return hnsecs(cast(long) length); 1340 else 1341 Throw(); 1342 assert(0); 1343 } 1344 /// ditto 1345 PackedDateTime getDateTime() const { 1346 if(containsDateTime) 1347 return PackedDateTime(cast(long) length); 1348 else 1349 Throw(); 1350 assert(0); 1351 } 1352 1353 /// ditto 1354 DynamicDecimal getDecimal() const { 1355 if(containsDecimal) 1356 return DynamicDecimal(cast(long) length); 1357 else 1358 Throw(); 1359 assert(0); 1360 } 1361 1362 1363 /++ 1364 1365 +/ 1366 string toString() const { 1367 1368 string intHelper(string prefix, int radix) { 1369 char[128] buffer; 1370 buffer[0 .. prefix.length] = prefix[]; 1371 char[] toUse = buffer[prefix.length .. $]; 1372 1373 auto got = intToString(getInt(), toUse[], IntToStringArgs().withRadix(radix)); 1374 1375 return buffer[0 .. prefix.length + got.length].idup; 1376 } 1377 1378 with(Contains) 1379 final switch(contains()) { 1380 case null_: 1381 return "<null>"; 1382 case intDecimal: 1383 return intHelper("", 10); 1384 case intHex: 1385 return intHelper("0x", 16); 1386 case intBinary: 1387 return intHelper("0b", 2); 1388 case intOctal: 1389 return intHelper("0o", 8); 1390 case emptySso, stringSso, string, stringz: 1391 return getString().idup; 1392 case bytes: 1393 auto b = getBytes(); 1394 1395 return "<bytes>"; // FIXME 1396 case object: 1397 auto o = getObject(); 1398 return o is null ? "null" : o.toString(); 1399 case monoTime: 1400 return getMonoTime.toString(); 1401 case utcTimestamp: 1402 return getUtcTimestamp().toString(); 1403 case duration: 1404 return getDuration().toString(); 1405 case dateTime: 1406 return getDateTime().toString(); 1407 case decimal: 1408 return getDecimal().toString(); 1409 case double_: 1410 auto d = getDouble(); 1411 1412 import core.stdc.stdio; 1413 char[64] buffer; 1414 auto count = snprintf(buffer.ptr, buffer.length, "%.17lf", d); 1415 return buffer[0 .. count].idup; 1416 case invalid: 1417 return "<invalid>"; 1418 } 1419 } 1420 1421 /++ 1422 Note for integral types that are not `int` and `long` (for example, `short` or `ubyte`), you might want to explicitly convert them to `int`. 1423 +/ 1424 this(string s) { 1425 ptr = cast(const(ubyte)*) s.ptr; 1426 length = cast(void*) s.length; 1427 } 1428 1429 /// ditto 1430 this(const(char)* stringz) { 1431 if(stringz !is null) { 1432 ptr = cast(const(ubyte)*) stringz; 1433 length = cast(void*) size_t.max; 1434 } else { 1435 ptr = null; 1436 length = null; 1437 } 1438 } 1439 1440 /// ditto 1441 this(const(ubyte)[] b) { 1442 ptr = cast(const(ubyte)*) b.ptr; 1443 length = cast(void*) (b.length | (1UL << (size_t.sizeof * 8 - 1))); 1444 } 1445 1446 /// ditto 1447 this(long l, int base = 10) { 1448 int tag; 1449 switch(base) { 1450 case 10: tag = 1; break; 1451 case 16: tag = 2; break; 1452 case 2: tag = 3; break; 1453 case 8: tag = 4; break; 1454 default: assert(0, "You passed an invalid base to LimitedVariant"); 1455 } 1456 ptr = cast(ubyte*) tag; 1457 length = cast(void*) l; 1458 } 1459 1460 /// ditto 1461 this(int i, int base = 10) { 1462 this(cast(long) i, base); 1463 } 1464 1465 /// ditto 1466 this(bool i) { 1467 // FIXME? 1468 this(cast(long) i); 1469 } 1470 1471 /// ditto 1472 this(double d) { 1473 // the reinterpret cast hack crashes dmd! omg 1474 ptr = cast(ubyte*) 5; 1475 1476 floathack h; 1477 h.d = d; 1478 1479 this.length = h.e; 1480 } 1481 1482 /// ditto 1483 this(Object o) { 1484 this.ptr = cast(ubyte*) 6; 1485 this.length = cast(void*) o; 1486 } 1487 1488 /// ditto 1489 this(MonoTime a) { 1490 this.ptr = cast(ubyte*) 7; 1491 this.length = cast(void*) a.ticks; 1492 } 1493 1494 /// ditto 1495 this(SimplifiedUtcTimestamp a) { 1496 this.ptr = cast(ubyte*) 8; 1497 this.length = cast(void*) a.timestamp; 1498 } 1499 1500 /// ditto 1501 this(Duration a) { 1502 this.ptr = cast(ubyte*) 9; 1503 this.length = cast(void*) a.total!"hnsecs"; 1504 } 1505 1506 /// ditto 1507 this(PackedDateTime a) { 1508 this.ptr = cast(ubyte*) 10; 1509 this.length = cast(void*) a.packedData; 1510 } 1511 1512 /// ditto 1513 this(DynamicDecimal a) { 1514 this.ptr = cast(ubyte*) 11; 1515 this.length = cast(void*) a.storage; 1516 } 1517 } 1518 1519 unittest { 1520 LimitedVariant v = LimitedVariant("foo"); 1521 assert(v.containsString()); 1522 assert(!v.containsInt()); 1523 assert(v.getString() == "foo"); 1524 1525 LimitedVariant v2 = LimitedVariant(4); 1526 assert(v2.containsInt()); 1527 assert(!v2.containsString()); 1528 assert(v2.getInt() == 4); 1529 1530 LimitedVariant v3 = LimitedVariant(cast(ubyte[]) [1, 2, 3]); 1531 assert(v3.containsBytes()); 1532 assert(!v3.containsString()); 1533 assert(v3.getBytes() == [1, 2, 3]); 1534 } 1535 1536 private union floathack { 1537 // in 32 bit we'll use float instead since it at least fits in the void* 1538 static if(double.sizeof == (void*).sizeof) { 1539 double d; 1540 } else { 1541 float d; 1542 } 1543 void* e; 1544 } 1545 1546 /+ 1547 64 bit signed goes up to 9.22x10^18 1548 1549 3 bit precision = 0-7 1550 60 bits remain for the value = 1.15x10^18. 1551 1552 so you can use up to 10 digits decimal 7 digits. 1553 1554 9,999,999,999.9999999 1555 1556 math between decimals must always have the same precision on both sides. 1557 1558 decimal and 32 bit int is always allowed assuming the int is a whole number. 1559 1560 FIXME add this to LimitedVariant 1561 +/ 1562 /++ 1563 A DynamicDecimal is a fixed-point object whose precision is dynamically typed. 1564 1565 1566 It packs everything into a 64 bit value. It uses one bit for sign, three bits 1567 for precision, then the rest of them for the value. This means the precision 1568 (that is, the number of digits after the decimal) can be from 0 to 7, and there 1569 can be a total of 18 digits. 1570 1571 Numbers can be added and subtracted only if they have matching precision. They can 1572 be multiplied and divided only by whole numbers. 1573 1574 History: 1575 Added December 12, 2025. 1576 +/ 1577 struct DynamicDecimal { 1578 private ulong storage; 1579 1580 private this(ulong storage) { 1581 this.storage = storage; 1582 } 1583 1584 this(long value, int precision) { 1585 assert(precision >= 0 && precision <= 7); 1586 bool isNeg = value < 0; 1587 if(isNeg) 1588 value = -value; 1589 assert((value & 0xf000_0000_0000_0000) == 0); 1590 1591 storage = 1592 (isNeg ? 0x8000_0000_0000_0000 : 0) 1593 | 1594 (cast(ulong) precision << 60) 1595 | 1596 (value) 1597 ; 1598 } 1599 1600 private bool isNegative() { 1601 return (storage >> 63) ? true : false; 1602 } 1603 1604 /++ 1605 +/ 1606 int precision() { 1607 return (storage >> 60) & 7; 1608 } 1609 1610 /++ 1611 +/ 1612 long value() { 1613 long omg = storage & 0x0fff_ffff_ffff_ffff; 1614 if(isNegative) 1615 omg = -omg; 1616 return omg; 1617 } 1618 1619 /++ 1620 Some basic arithmetic operators are defined on this: +, -, *, and /, but only between 1621 numbers of the same precision. Note that division always returns the quotient and remainder 1622 together in one return and any overflowing operations will also throw. 1623 +/ 1624 typeof(this) opBinary(string op)(typeof(this) rhs) if(op == "+" || op == "-") { 1625 assert(this.precision == rhs.precision); 1626 return typeof(this)(mixin("this.value" ~ op ~ "rhs.value"), this.precision); 1627 } 1628 1629 /// ditto 1630 typeof(this) opBinary(string op)(int rhs) if(op == "*") { 1631 // what if we overflow on the multiplication? FIXME 1632 return typeof(this)(this.value * rhs, this.precision); 1633 } 1634 1635 /// ditto 1636 static struct DivisionResult { 1637 DynamicDecimal quotient; 1638 DynamicDecimal remainder; 1639 } 1640 1641 /// ditto 1642 DivisionResult opBinary(string op)(int rhs) if(op == "/") { 1643 return DivisionResult(typeof(this)(this.value / rhs, this.precision), typeof(this)(this.value % rhs, this.precision)); 1644 } 1645 1646 /// ditto 1647 typeof(this) opUnary(string op : "-")() { 1648 return typeof(this)(-this.value, this.precision); 1649 } 1650 1651 /// ditto 1652 long opCmp(typeof(this) rhs) { 1653 assert(this.precision == rhs.precision); 1654 return this.value - rhs.value; 1655 } 1656 1657 /++ 1658 Converts to a floating point type. There's potentially a loss of precision here. 1659 +/ 1660 double toFloatingPoint() { 1661 long divisor = 1; 1662 foreach(i; 0 .. this.precision) 1663 divisor *= 10; 1664 return cast(double) this.value / divisor; 1665 } 1666 1667 /++ 1668 +/ 1669 string toString(int minimumNumberOfDigitsLeftOfDecimal = 1) @system { 1670 char[64] buffer = void; 1671 // FIXME: what about a group separator arg? 1672 IntToStringArgs args = IntToStringArgs(). 1673 withPadding(minimumNumberOfDigitsLeftOfDecimal + this.precision); 1674 auto got = intToString(this.value, buffer[], args); 1675 assert(got.length >= this.precision); 1676 int digitsLeftOfDecimal = cast(int) got.length - this.precision; 1677 auto toShift = buffer[got.length - this.precision .. got.length]; 1678 import core.stdc.string; 1679 memmove(toShift.ptr + 1, toShift.ptr, toShift.length); 1680 toShift[0] = '.'; 1681 return buffer[0 .. got.length + 1].idup; 1682 } 1683 } 1684 1685 unittest { 1686 DynamicDecimal a = DynamicDecimal(100, 2); 1687 auto res = a / 3; 1688 assert(res.quotient.value == 33); 1689 assert(res.remainder.value == 1); 1690 res = a / 2; 1691 assert(res.quotient.value == 50); 1692 assert(res.remainder.value == 0); 1693 1694 assert(res.quotient.toFloatingPoint == 0.50); 1695 assert(res.quotient.toString() == "0.50"); 1696 1697 assert((a * 2).value == 200); 1698 1699 DynamicDecimal b = DynamicDecimal(1, 4); 1700 assert(b.toFloatingPoint() == 0.0001); 1701 assert(b.toString() == "0.0001"); 1702 1703 assert(a > (a / 2).quotient); 1704 } 1705 1706 /++ 1707 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. 1708 +/ 1709 struct ArgSentinel {} 1710 1711 /++ 1712 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. 1713 1714 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. 1715 1716 Remember to `free` the returned pointer with `core.stdc.stdlib.free(ret.ptr);` 1717 1718 $(TIP 1719 I strongly recommend you simply use the normal garbage collector unless you have a very specific reason not to. 1720 ) 1721 1722 See_Also: 1723 [mallocedStringz] 1724 +/ 1725 T[] mallocSlice(T)(size_t n) { 1726 import c = core.stdc.stdlib; 1727 1728 return (cast(T*) c.malloc(n * T.sizeof))[0 .. n]; 1729 } 1730 1731 /++ 1732 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)`. 1733 1734 $(TIP 1735 I strongly recommend you use [CharzBuffer] or Phobos' [std.string.toStringz] instead unless there's a special reason not to. 1736 ) 1737 1738 See_Also: 1739 [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). 1740 1741 [mallocSlice] is the function this function calls, so the notes in its documentation applies here too. 1742 +/ 1743 char[] mallocedStringz(in char[] original) { 1744 auto slice = mallocSlice!char(original.length + 1); 1745 if(slice is null) 1746 return null; 1747 slice[0 .. original.length] = original[]; 1748 slice[original.length] = 0; 1749 return slice; 1750 } 1751 1752 /++ 1753 Basically a `scope class` you can return from a function or embed in another aggregate. 1754 +/ 1755 struct OwnedClass(Class) { 1756 ubyte[__traits(classInstanceSize, Class)] rawData; 1757 1758 static OwnedClass!Class defaultConstructed() { 1759 OwnedClass!Class i = OwnedClass!Class.init; 1760 i.initializeRawData(); 1761 return i; 1762 } 1763 1764 private void initializeRawData() @trusted { 1765 if(!this) 1766 rawData[] = cast(ubyte[]) typeid(Class).initializer[]; 1767 } 1768 1769 this(T...)(T t) { 1770 initializeRawData(); 1771 rawInstance.__ctor(t); 1772 } 1773 1774 bool opCast(T : bool)() @trusted { 1775 return !(*(cast(void**) rawData.ptr) is null); 1776 } 1777 1778 @disable this(); 1779 @disable this(this); 1780 1781 Class rawInstance() return @trusted { 1782 if(!this) 1783 throw new Exception("null"); 1784 return cast(Class) rawData.ptr; 1785 } 1786 1787 alias rawInstance this; 1788 1789 ~this() @trusted { 1790 if(this) 1791 .destroy(rawInstance()); 1792 } 1793 } 1794 1795 // might move RecyclableMemory here 1796 1797 version(Posix) 1798 package(arsd) void makeNonBlocking(int fd) { 1799 import core.sys.posix.fcntl; 1800 auto flags = fcntl(fd, F_GETFL, 0); 1801 if(flags == -1) 1802 throw new ErrnoApiException("fcntl get", errno); 1803 flags |= O_NONBLOCK; 1804 auto s = fcntl(fd, F_SETFL, flags); 1805 if(s == -1) 1806 throw new ErrnoApiException("fcntl set", errno); 1807 } 1808 1809 version(Posix) 1810 package(arsd) void setCloExec(int fd) { 1811 import core.sys.posix.fcntl; 1812 auto flags = fcntl(fd, F_GETFD, 0); 1813 if(flags == -1) 1814 throw new ErrnoApiException("fcntl get", errno); 1815 flags |= FD_CLOEXEC; 1816 auto s = fcntl(fd, F_SETFD, flags); 1817 if(s == -1) 1818 throw new ErrnoApiException("fcntl set", errno); 1819 } 1820 1821 1822 /++ 1823 A helper object for temporarily constructing a string appropriate for the Windows API from a D UTF-8 string. 1824 1825 1826 It will use a small internal static buffer is possible, and allocate a new buffer if the string is too big. 1827 1828 History: 1829 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1830 +/ 1831 version(Windows) 1832 struct WCharzBuffer { 1833 private wchar[] buffer; 1834 private wchar[128] staticBuffer = void; 1835 1836 /// Length of the string, excluding the zero terminator. 1837 size_t length() { 1838 return buffer.length; 1839 } 1840 1841 // Returns the pointer to the internal buffer. You must assume its lifetime is less than that of the WCharzBuffer. It is zero-terminated. 1842 wchar* ptr() { 1843 return buffer.ptr; 1844 } 1845 1846 /// 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. 1847 wchar[] slice() { 1848 return buffer; 1849 } 1850 1851 /// Copies it into a static array of wchars 1852 void copyInto(R)(ref R r) { 1853 static if(is(R == wchar[N], size_t N)) { 1854 r[0 .. this.length] = slice[]; 1855 r[this.length] = 0; 1856 } else static assert(0, "can only copy into wchar[n], not " ~ R.stringof); 1857 } 1858 1859 /++ 1860 conversionFlags = [WindowsStringConversionFlags] 1861 +/ 1862 this(in char[] data, int conversionFlags = 0) { 1863 conversionFlags |= WindowsStringConversionFlags.zeroTerminate; // this ALWAYS zero terminates cuz of its name 1864 auto sz = sizeOfConvertedWstring(data, conversionFlags); 1865 if(sz > staticBuffer.length) 1866 buffer = new wchar[](sz); 1867 else 1868 buffer = staticBuffer[]; 1869 1870 buffer = makeWindowsString(data, buffer, conversionFlags); 1871 } 1872 } 1873 1874 /++ 1875 Alternative for toStringz 1876 1877 History: 1878 Added March 18, 2023 (dub v11.0) 1879 +/ 1880 struct CharzBuffer { 1881 private char[] buffer; 1882 private char[128] staticBuffer = void; 1883 1884 /// Length of the string, excluding the zero terminator. 1885 size_t length() { 1886 assert(buffer.length > 0); 1887 return buffer.length - 1; 1888 } 1889 1890 // Returns the pointer to the internal buffer. You must assume its lifetime is less than that of the CharzBuffer. It is zero-terminated. 1891 char* ptr() { 1892 return buffer.ptr; 1893 } 1894 1895 /// 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. 1896 char[] slice() { 1897 assert(buffer.length > 0); 1898 return buffer[0 .. $-1]; 1899 } 1900 1901 /// Copies it into a static array of chars 1902 void copyInto(R)(ref R r) { 1903 static if(is(R == char[N], size_t N)) { 1904 r[0 .. this.length] = slice[]; 1905 r[this.length] = 0; 1906 } else static assert(0, "can only copy into char[n], not " ~ R.stringof); 1907 } 1908 1909 @disable this(); 1910 @disable this(this); 1911 1912 /++ 1913 Copies `data` into the CharzBuffer, allocating a new one if needed, and zero-terminates it. 1914 +/ 1915 this(in char[] data) { 1916 if(data.length + 1 > staticBuffer.length) 1917 buffer = new char[](data.length + 1); 1918 else 1919 buffer = staticBuffer[]; 1920 1921 buffer[0 .. data.length] = data[]; 1922 buffer[data.length] = 0; 1923 buffer = buffer[0 .. data.length + 1]; 1924 } 1925 } 1926 1927 /++ 1928 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. 1929 1930 History: 1931 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1932 +/ 1933 version(Windows) 1934 wchar[] makeWindowsString(in char[] str, wchar[] buffer, int conversionFlags = WindowsStringConversionFlags.zeroTerminate) { 1935 if(str.length == 0) 1936 return null; 1937 1938 int pos = 0; 1939 dchar last; 1940 foreach(dchar c; str) { 1941 if(c <= 0xFFFF) { 1942 if((conversionFlags & WindowsStringConversionFlags.convertNewLines) && c == 10 && last != 13) 1943 buffer[pos++] = 13; 1944 buffer[pos++] = cast(wchar) c; 1945 } else if(c <= 0x10FFFF) { 1946 buffer[pos++] = cast(wchar)((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); 1947 buffer[pos++] = cast(wchar)(((c - 0x10000) & 0x3FF) + 0xDC00); 1948 } 1949 1950 last = c; 1951 } 1952 1953 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) { 1954 buffer[pos] = 0; 1955 } 1956 1957 return buffer[0 .. pos]; 1958 } 1959 1960 /++ 1961 Converts the Windows API string `str` to a D UTF-8 string, storing it in `buffer`. Returns the slice of `buffer` actually used. 1962 1963 History: 1964 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1965 +/ 1966 version(Windows) 1967 char[] makeUtf8StringFromWindowsString(in wchar[] str, char[] buffer) { 1968 if(str.length == 0) 1969 return null; 1970 1971 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, buffer.ptr, cast(int) buffer.length, null, null); 1972 if(got == 0) { 1973 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 1974 throw new object.Exception("not enough buffer"); 1975 else 1976 throw new object.Exception("conversion"); // FIXME: GetLastError 1977 } 1978 return buffer[0 .. got]; 1979 } 1980 1981 /++ 1982 Converts the Windows API string `str` to a newly-allocated D UTF-8 string. 1983 1984 History: 1985 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 1986 +/ 1987 version(Windows) 1988 string makeUtf8StringFromWindowsString(in wchar[] str) { 1989 char[] buffer; 1990 auto got = WideCharToMultiByte(CP_UTF8, 0, str.ptr, cast(int) str.length, null, 0, null, null); 1991 buffer.length = got; 1992 1993 // it is unique because we just allocated it above! 1994 return cast(string) makeUtf8StringFromWindowsString(str, buffer); 1995 } 1996 1997 /// ditto 1998 version(Windows) 1999 string makeUtf8StringFromWindowsString(wchar* str) { 2000 char[] buffer; 2001 auto got = WideCharToMultiByte(CP_UTF8, 0, str, -1, null, 0, null, null); 2002 buffer.length = got; 2003 2004 got = WideCharToMultiByte(CP_UTF8, 0, str, -1, buffer.ptr, cast(int) buffer.length, null, null); 2005 if(got == 0) { 2006 if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 2007 throw new object.Exception("not enough buffer"); 2008 else 2009 throw new object.Exception("conversion"); // FIXME: GetLastError 2010 } 2011 return cast(string) buffer[0 .. got]; 2012 } 2013 2014 // only used from minigui rn 2015 package int findIndexOfZero(in wchar[] str) { 2016 foreach(idx, wchar ch; str) 2017 if(ch == 0) 2018 return cast(int) idx; 2019 return cast(int) str.length; 2020 } 2021 package int findIndexOfZero(in char[] str) { 2022 foreach(idx, char ch; str) 2023 if(ch == 0) 2024 return cast(int) idx; 2025 return cast(int) str.length; 2026 } 2027 2028 /++ 2029 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. 2030 2031 History: 2032 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 2033 +/ 2034 version(Windows) 2035 int sizeOfConvertedWstring(in char[] s, int conversionFlags) { 2036 int size = 0; 2037 2038 if(conversionFlags & WindowsStringConversionFlags.convertNewLines) { 2039 // need to convert line endings, which means the length will get bigger. 2040 2041 // BTW I betcha this could be faster with some simd stuff. 2042 char last; 2043 foreach(char ch; s) { 2044 if(ch == 10 && last != 13) 2045 size++; // will add a 13 before it... 2046 size++; 2047 last = ch; 2048 } 2049 } else { 2050 // no conversion necessary, just estimate based on length 2051 /* 2052 I don't think there's any string with a longer length 2053 in code units when encoded in UTF-16 than it has in UTF-8. 2054 This will probably over allocate, but that's OK. 2055 */ 2056 size = cast(int) s.length; 2057 } 2058 2059 if(conversionFlags & WindowsStringConversionFlags.zeroTerminate) 2060 size++; 2061 2062 return size; 2063 } 2064 2065 /++ 2066 Used by [makeWindowsString] and [WCharzBuffer] 2067 2068 History: 2069 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 2070 +/ 2071 version(Windows) 2072 enum WindowsStringConversionFlags : int { 2073 /++ 2074 Append a zero terminator to the string. 2075 +/ 2076 zeroTerminate = 1, 2077 /++ 2078 Converts newlines from \n to \r\n. 2079 +/ 2080 convertNewLines = 2, 2081 } 2082 2083 /++ 2084 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. 2085 2086 The buffer must be sized to hold the converted number. 32 chars is enough for most anything. 2087 2088 Returns: the slice of `buffer` containing the converted number. 2089 +/ 2090 char[] intToString(long value, char[] buffer, IntToStringArgs args = IntToStringArgs.init) { 2091 const int radix = args.radix ? args.radix : 10; 2092 const int digitsPad = args.padTo; 2093 const int groupSize = args.groupSize; 2094 2095 int pos; 2096 2097 bool needsOverflowFixup = false; 2098 2099 if(value == long.min) { 2100 // -long.min will overflow so we're gonna cheat 2101 value += 1; 2102 needsOverflowFixup = true; 2103 } 2104 2105 if(value < 0) { 2106 buffer[pos++] = '-'; 2107 value = -value; 2108 } 2109 2110 int start = pos; 2111 int digitCount; 2112 int groupCount; 2113 2114 void outputDigit(char c) { 2115 if(groupSize && groupCount == groupSize) { 2116 buffer[pos++] = args.separator; 2117 groupCount = 0; 2118 } 2119 2120 buffer[pos++] = c; 2121 groupCount++; 2122 digitCount++; 2123 2124 } 2125 2126 do { 2127 auto remainder = value % radix; 2128 value = value / radix; 2129 if(needsOverflowFixup) { 2130 if(remainder + 1 == radix) { 2131 outputDigit('0'); 2132 remainder = 0; 2133 value += 1; 2134 } else { 2135 remainder += 1; 2136 } 2137 needsOverflowFixup = false; 2138 } 2139 2140 outputDigit(cast(char) (remainder < 10 ? (remainder + '0') : (remainder - 10 + args.ten))); 2141 } while(value); 2142 2143 if(digitsPad > 0) { 2144 while(digitCount < digitsPad) { 2145 if(groupSize && groupCount == groupSize) { 2146 buffer[pos++] = args.separator; 2147 groupCount = 0; 2148 } 2149 buffer[pos++] = args.padWith; 2150 digitCount++; 2151 groupCount++; 2152 } 2153 } 2154 2155 assert(pos >= 1); 2156 assert(pos - start > 0); 2157 2158 auto reverseSlice = buffer[start .. pos]; 2159 for(int i = 0; i < reverseSlice.length / 2; i++) { 2160 auto paired = cast(int) reverseSlice.length - i - 1; 2161 char tmp = reverseSlice[i]; 2162 reverseSlice[i] = reverseSlice[paired]; 2163 reverseSlice[paired] = tmp; 2164 } 2165 2166 return buffer[0 .. pos]; 2167 } 2168 2169 /// ditto 2170 struct IntToStringArgs { 2171 private { 2172 ubyte padTo; 2173 char padWith; 2174 ubyte radix; 2175 char ten; 2176 ubyte groupSize; 2177 char separator; 2178 } 2179 2180 IntToStringArgs withPadding(int padTo, char padWith = '0') { 2181 IntToStringArgs args = this; 2182 args.padTo = cast(ubyte) padTo; 2183 args.padWith = padWith; 2184 return args; 2185 } 2186 2187 IntToStringArgs withRadix(int radix, char ten = 'a') { 2188 IntToStringArgs args = this; 2189 args.radix = cast(ubyte) radix; 2190 args.ten = ten; 2191 return args; 2192 } 2193 2194 IntToStringArgs withGroupSeparator(int groupSize, char separator = '_') { 2195 IntToStringArgs args = this; 2196 args.groupSize = cast(ubyte) groupSize; 2197 args.separator = separator; 2198 return args; 2199 } 2200 } 2201 2202 struct FloatToStringArgs { 2203 private { 2204 // whole number component 2205 ubyte padTo; 2206 char padWith; 2207 ubyte groupSize; 2208 char separator; 2209 2210 // for the fractional component 2211 ubyte minimumPrecision = 0; // will always show at least this many digits after the decimal (if it is 0 there may be no decimal) 2212 ubyte maximumPrecision = 32; // will round to this many after the decimal 2213 2214 bool useScientificNotation; // if this is true, note the whole number component will always be exactly one digit, so the pad stuff applies to the exponent only and it assumes pad with zero's to two digits 2215 } 2216 2217 FloatToStringArgs withPadding(int padTo, char padWith = '0') { 2218 FloatToStringArgs args = this; 2219 args.padTo = cast(ubyte) padTo; 2220 args.padWith = padWith; 2221 return args; 2222 } 2223 2224 FloatToStringArgs withGroupSeparator(int groupSize, char separator = '_') { 2225 FloatToStringArgs args = this; 2226 args.groupSize = cast(ubyte) groupSize; 2227 args.separator = separator; 2228 return args; 2229 } 2230 2231 FloatToStringArgs withPrecision(int minDigits, int maxDigits = 0) { 2232 FloatToStringArgs args = this; 2233 args.minimumPrecision = cast(ubyte) minDigits; 2234 if(maxDigits < minDigits) 2235 maxDigits = minDigits; 2236 args.maximumPrecision = cast(ubyte) maxDigits; 2237 return args; 2238 } 2239 2240 FloatToStringArgs withScientificNotation(bool enabled) { 2241 FloatToStringArgs args = this; 2242 args.useScientificNotation = enabled; 2243 return args; 2244 } 2245 } 2246 2247 // the buffer should be at least 32 bytes long, maybe more with other args 2248 char[] floatToString(double value, char[] buffer, FloatToStringArgs args = FloatToStringArgs.init) { 2249 // actually doing this is pretty painful, so gonna pawn it off on the C lib 2250 import core.stdc.stdio; 2251 // FIXME: what if there's a locale in place that changes the decimal point? 2252 auto ret = snprintf(buffer.ptr, buffer.length, args.useScientificNotation ? "%.*e" : "%.*f", args.maximumPrecision, value); 2253 if(!args.useScientificNotation && (args.padTo || args.groupSize)) { 2254 char[32] scratch = void; 2255 auto idx = buffer[0 .. ret].indexOf("."); 2256 2257 int digitsOutput = 0; 2258 int digitsGrouped = 0; 2259 if(idx > 0) { 2260 // there is a whole number component 2261 int pos = cast(int) scratch.length; 2262 2263 auto splitPoint = idx; 2264 2265 while(idx) { 2266 if(args.groupSize && digitsGrouped == args.groupSize) { 2267 scratch[--pos] = args.separator; 2268 digitsGrouped = 0; 2269 } 2270 scratch[--pos] = buffer[--idx]; 2271 2272 digitsOutput++; 2273 digitsGrouped++; 2274 } 2275 2276 if(args.padTo) 2277 while(digitsOutput < args.padTo) { 2278 if(args.groupSize && digitsGrouped == args.groupSize) { 2279 scratch[--pos] = args.separator; 2280 digitsGrouped = 0; 2281 } 2282 2283 scratch[--pos] = args.padWith; 2284 2285 digitsOutput++; 2286 digitsGrouped++; 2287 } 2288 2289 char[32] remainingBuffer; 2290 remainingBuffer[0 .. ret - splitPoint]= buffer[splitPoint .. ret]; 2291 2292 buffer[0 .. scratch.length - pos] = scratch[pos .. $]; 2293 buffer[scratch.length - pos .. scratch.length - pos + ret - splitPoint] = remainingBuffer[0 .. ret - splitPoint]; 2294 2295 ret = cast(int) scratch.length - pos + ret - splitPoint; 2296 } 2297 } 2298 2299 // sprintf will always put zeroes on to the maximum precision, but if it is a bunch of trailing zeroes, we can trim them 2300 // if scientific notation, don't forget to bring the e back down though. 2301 int trailingZeroesStart = -1; 2302 int dot = -1; 2303 int trailingZeroesEnd; 2304 bool inZone; 2305 foreach(idx, ch; buffer[0 .. ret]) { 2306 if(inZone) { 2307 if(ch == '0') { 2308 if(trailingZeroesStart == -1) { 2309 trailingZeroesStart = cast(int) idx; 2310 } 2311 } else if(ch == 'e') { 2312 trailingZeroesEnd = cast(int) idx; 2313 break; 2314 } else { 2315 trailingZeroesStart = -1; 2316 } 2317 } else { 2318 if(ch == '.') { 2319 inZone = true; 2320 dot = cast(int) idx; 2321 } 2322 } 2323 } 2324 if(trailingZeroesEnd == 0) 2325 trailingZeroesEnd = ret; 2326 2327 // 0.430000 2328 // end = $ 2329 // dot = 1 2330 // start = 4 2331 // precision is thus 3-1 = 2 2332 // if min precision = 0 2333 if(dot != -1 && trailingZeroesStart > dot) { 2334 auto currentPrecision = trailingZeroesStart - dot - 1; 2335 auto precWanted = (args.minimumPrecision > currentPrecision) ? args.minimumPrecision : currentPrecision; 2336 auto sliceOffset = dot + precWanted + 1; 2337 if(precWanted == 0) 2338 sliceOffset -= 1; // remove the dot 2339 char[] keep = buffer[trailingZeroesEnd .. ret]; 2340 2341 // slice copy doesn't allow overlapping and since it can, we need to memmove 2342 //buffer[sliceOffset .. sliceOffset + keep.length] = keep[]; 2343 import core.stdc.string; 2344 memmove(buffer[sliceOffset .. ret].ptr, keep.ptr, keep.length); 2345 2346 ret = cast(int) (sliceOffset + keep.length); 2347 } 2348 /+ 2349 if(minimumPrecision > 0) { 2350 auto idx = buffer[0 .. ret].indexOf("."); 2351 if(idx == -1) { 2352 buffer[ret++] = '.'; 2353 idx = ret; 2354 } 2355 2356 while(ret - idx < minimumPrecision) 2357 buffer[ret++] = '0'; 2358 } 2359 +/ 2360 return buffer[0 .. ret]; 2361 } 2362 2363 unittest { 2364 char[32] buffer; 2365 assert(intToString(0, buffer[]) == "0"); 2366 assert(intToString(-1, buffer[]) == "-1"); 2367 assert(intToString(-132, buffer[]) == "-132"); 2368 assert(intToString(-1932, buffer[]) == "-1932"); 2369 assert(intToString(1, buffer[]) == "1"); 2370 assert(intToString(132, buffer[]) == "132"); 2371 assert(intToString(1932, buffer[]) == "1932"); 2372 2373 assert(intToString(0x1, buffer[], IntToStringArgs().withRadix(16)) == "1"); 2374 assert(intToString(0x1b, buffer[], IntToStringArgs().withRadix(16)) == "1b"); 2375 assert(intToString(0xef1, buffer[], IntToStringArgs().withRadix(16)) == "ef1"); 2376 2377 assert(intToString(0xef1, buffer[], IntToStringArgs().withRadix(16).withPadding(8)) == "00000ef1"); 2378 assert(intToString(-0xef1, buffer[], IntToStringArgs().withRadix(16).withPadding(8)) == "-00000ef1"); 2379 assert(intToString(-0xef1, buffer[], IntToStringArgs().withRadix(16, 'A').withPadding(8, ' ')) == "- EF1"); 2380 2381 assert(intToString(4000, buffer[], IntToStringArgs().withPadding(4).withGroupSeparator(3, ',')) == "4,000"); 2382 assert(intToString(400, buffer[], IntToStringArgs().withPadding(4).withGroupSeparator(3, ',')) == "0,400"); 2383 2384 const pi = 3.14159256358979; 2385 assert(floatToString(pi, buffer[], FloatToStringArgs().withPrecision(3)) == "3.142"); 2386 assert(floatToString(pi, buffer[], FloatToStringArgs().withPrecision(2)) == "3.14"); 2387 assert(floatToString(pi, buffer[], FloatToStringArgs().withPrecision(0)) == "3"); 2388 2389 assert(floatToString(4.0, buffer[], FloatToStringArgs().withPrecision(0)) == "4"); 2390 assert(floatToString(4.0, buffer[], FloatToStringArgs().withPrecision(3)) == "4.000"); 2391 2392 assert(floatToString(4.0, buffer[], FloatToStringArgs().withPadding(3).withPrecision(3)) == "004.000"); 2393 assert(floatToString(4.0, buffer[], FloatToStringArgs().withPadding(3).withGroupSeparator(3, ',').withPrecision(3)) == "004.000"); 2394 assert(floatToString(4.0, buffer[], FloatToStringArgs().withPadding(4).withGroupSeparator(3, ',').withPrecision(3)) == "0,004.000"); 2395 assert(floatToString(4000.0, buffer[], FloatToStringArgs().withPadding(4).withGroupSeparator(3, ',').withPrecision(3)) == "4,000.000"); 2396 2397 assert(floatToString(4.25, buffer[], FloatToStringArgs().withPrecision(3, 5)) == "4.250"); 2398 assert(floatToString(4.25, buffer[], FloatToStringArgs().withPrecision(2, 5)) == "4.25"); 2399 assert(floatToString(4.25, buffer[], FloatToStringArgs().withPrecision(0, 5)) == "4.25"); 2400 assert(floatToString(4.25, buffer[], FloatToStringArgs().withPrecision(0)) == "4"); 2401 assert(floatToString(4.251, buffer[], FloatToStringArgs().withPrecision(1)) == "4.3"); // 2.25 would be rounded to even and thus be 2.2... sometimes. this less ambiguous 2402 2403 //assert(floatToString(4.25, buffer[], FloatToStringArgs().withPrecision(1)) == "4.2"); 2404 //assert(floatToString(4.35, buffer[], FloatToStringArgs().withPrecision(1)) == "4.3"); 2405 /+ 2406 import core.stdc.stdio; 2407 printf("%.1f\n", 4.25); // 4.2 2408 printf("%.1f\n", 4.35); // 4.3 2409 +/ 2410 2411 assert(floatToString(pi*10, buffer[], FloatToStringArgs().withPrecision(2).withScientificNotation(true)) == "3.14e+01"); 2412 2413 assert(floatToString(500, buffer[], FloatToStringArgs().withPrecision(0, 2).withScientificNotation(true)) == "5e+02"); 2414 } 2415 2416 /++ 2417 History: 2418 Moved from color.d to core.d in March 2023 (dub v11.0). 2419 +/ 2420 nothrow @safe @nogc pure 2421 inout(char)[] stripInternal(return inout(char)[] s) { 2422 bool isAllWhitespace = true; 2423 foreach(i, char c; s) 2424 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 2425 isAllWhitespace = false; 2426 s = s[i .. $]; 2427 break; 2428 } 2429 if(isAllWhitespace) 2430 return s[0 .. 0]; 2431 2432 foreach_reverse(i, char c; s) { 2433 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 2434 s = s[0 .. i + 1]; 2435 break; 2436 } 2437 } 2438 2439 return s; 2440 } 2441 2442 /// ditto 2443 nothrow @safe @nogc pure 2444 inout(char)[] stripRightInternal(return inout(char)[] s) { 2445 bool isAllWhitespace = true; 2446 foreach_reverse(a, c; s) { 2447 if(c != ' ' && c != '\t' && c != '\n' && c != '\r') { 2448 s = s[0 .. a + 1]; 2449 isAllWhitespace = false; 2450 break; 2451 } 2452 } 2453 if(isAllWhitespace) 2454 s = s[0..0]; 2455 2456 return s; 2457 2458 } 2459 2460 /++ 2461 Shortcut for converting some types to string without invoking Phobos (but it may as a last resort). 2462 2463 History: 2464 Moved from color.d to core.d in March 2023 (dub v11.0). 2465 +/ 2466 string toStringInternal(T)(T t) { 2467 char[256] bufferBacking; 2468 return writeGuts(bufferBacking[], null, null, null, false, false, &makeString, t); 2469 /+ 2470 char[64] buffer; 2471 static if(is(typeof(t.toString) : string)) 2472 return t.toString(); 2473 else static if(is(T : string)) 2474 return t; 2475 else static if(is(T == enum)) { 2476 switch(t) { 2477 foreach(memberName; __traits(allMembers, T)) { 2478 case __traits(getMember, T, memberName): 2479 return memberName; 2480 } 2481 default: 2482 return "<unknown>"; 2483 } 2484 } else static if(is(T : long)) { 2485 return intToString(t, buffer[]).idup; 2486 } else static if(is(T : const E[], E)) { 2487 string ret = "["; 2488 foreach(idx, e; t) { 2489 if(idx) 2490 ret ~= ", "; 2491 ret ~= toStringInternal(e); 2492 } 2493 ret ~= "]"; 2494 return ret; 2495 } else static if(is(T : double)) { 2496 import core.stdc.stdio; 2497 auto ret = snprintf(buffer.ptr, buffer.length, "%f", t); 2498 return buffer[0 .. ret].idup; 2499 } else { 2500 static assert(0, T.stringof ~ " makes compile too slow"); 2501 // import std.conv; return to!string(t); 2502 } 2503 +/ 2504 } 2505 2506 unittest { 2507 assert(toStringInternal(-43) == "-43"); 2508 assert(toStringInternal(4.5) == "4.5"); 2509 } 2510 2511 char[] toTextBuffer(T...)(char[] bufferBacking, T t) { 2512 return cast(char[]) writeGuts(bufferBacking[], null, null, null, false, false, &makeStringCasting, t); 2513 } 2514 2515 /++ 2516 2517 +/ 2518 string flagsToString(Flags)(ulong value) { 2519 string r; 2520 2521 void add(string memberName) { 2522 if(r.length) 2523 r ~= " | "; 2524 r ~= memberName; 2525 } 2526 2527 string none = "<none>"; 2528 2529 foreach(memberName; __traits(allMembers, Flags)) { 2530 auto flag = cast(ulong) __traits(getMember, Flags, memberName); 2531 if(flag) { 2532 if((value & flag) == flag) 2533 add(memberName); 2534 } else { 2535 none = memberName; 2536 } 2537 } 2538 2539 if(r.length == 0) 2540 r = none; 2541 2542 return r; 2543 } 2544 2545 unittest { 2546 enum MyFlags { 2547 none = 0, 2548 a = 1, 2549 b = 2 2550 } 2551 2552 assert(flagsToString!MyFlags(3) == "a | b"); 2553 assert(flagsToString!MyFlags(0) == "none"); 2554 assert(flagsToString!MyFlags(2) == "b"); 2555 } 2556 2557 private enum dchar replacementDchar = '\uFFFD'; 2558 2559 package size_t encodeUtf8(out char[4] buf, dchar c) @safe pure { 2560 if (c <= 0x7F) 2561 { 2562 assert(isValidDchar(c)); 2563 buf[0] = cast(char) c; 2564 return 1; 2565 } 2566 if (c <= 0x7FF) 2567 { 2568 assert(isValidDchar(c)); 2569 buf[0] = cast(char)(0xC0 | (c >> 6)); 2570 buf[1] = cast(char)(0x80 | (c & 0x3F)); 2571 return 2; 2572 } 2573 if (c <= 0xFFFF) 2574 { 2575 if (0xD800 <= c && c <= 0xDFFF) 2576 c = replacementDchar; 2577 2578 assert(isValidDchar(c)); 2579 L3: 2580 buf[0] = cast(char)(0xE0 | (c >> 12)); 2581 buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 2582 buf[2] = cast(char)(0x80 | (c & 0x3F)); 2583 return 3; 2584 } 2585 if (c <= 0x10FFFF) 2586 { 2587 assert(isValidDchar(c)); 2588 buf[0] = cast(char)(0xF0 | (c >> 18)); 2589 buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); 2590 buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); 2591 buf[3] = cast(char)(0x80 | (c & 0x3F)); 2592 return 4; 2593 } 2594 2595 assert(!isValidDchar(c)); 2596 c = replacementDchar; 2597 goto L3; 2598 } 2599 2600 /++ 2601 If it fits in the provided buffer, it will use it, otherwise, it will reallocate as-needed with the append operator. 2602 2603 Returns: 2604 the slice of `buffer` actually used, or the newly allocated array, if it was necessary. 2605 History: 2606 Added November 14, 2025 2607 +/ 2608 char[] transcodeUtf(scope const wchar[] input, char[] buffer) { 2609 size_t pos; 2610 char[4] temp; 2611 foreach(dchar ch; input) { 2612 auto stride = encodeUtf8(temp, ch); 2613 if(pos + stride < buffer.length) { 2614 buffer[pos .. pos + stride] = temp[0 .. stride]; 2615 pos += stride; 2616 } else { 2617 char[] t = buffer[0 .. pos]; 2618 t ~= temp[0 .. stride]; 2619 buffer = t; 2620 } 2621 } 2622 return buffer[0 .. pos]; 2623 } 2624 2625 /// ditto 2626 char[] transcodeUtf(scope const dchar[] input, char[] buffer) { 2627 // yes, this function body is char-for-char identical to the 2628 // previous overload. i just don't want to use a template here. 2629 size_t pos; 2630 char[4] temp; 2631 foreach(dchar ch; input) { 2632 auto stride = encodeUtf8(temp, ch); 2633 if(pos + stride < buffer.length) { 2634 buffer[pos .. pos + stride] = temp[0 .. stride]; 2635 pos += stride; 2636 } else { 2637 char[] t = buffer[0 .. pos]; 2638 t ~= temp[0 .. stride]; 2639 buffer = t; 2640 } 2641 } 2642 return buffer[0 .. pos]; 2643 } 2644 2645 inout(char)[] transcodeUtf(inout(char)[] input) { 2646 // no change needed 2647 return input; 2648 } 2649 2650 private bool isValidDchar(dchar c) pure nothrow @safe @nogc 2651 { 2652 return c < 0xD800 || (c > 0xDFFF && c <= 0x10FFFF); 2653 } 2654 2655 // technically s is octets but meh 2656 package string encodeUriComponent(string s) { 2657 char[3] encodeChar(char c) { 2658 char[3] buffer; 2659 buffer[0] = '%'; 2660 2661 enum hexchars = "0123456789ABCDEF"; 2662 buffer[1] = hexchars[c >> 4]; 2663 buffer[2] = hexchars[c & 0x0f]; 2664 2665 return buffer; 2666 } 2667 2668 string n; 2669 size_t previous = 0; 2670 foreach(idx, char ch; s) { 2671 if( 2672 (ch >= 'A' && ch <= 'Z') 2673 || 2674 (ch >= 'a' && ch <= 'z') 2675 || 2676 (ch >= '0' && ch <= '9') 2677 || ch == '-' || ch == '_' || ch == '.' || ch == '~' // unreserved set 2678 || ch == '!' || ch == '*' || ch == '\''|| ch == '(' || ch == ')' // subdelims but allowed in uri component (phobos also no encode them) 2679 ) { 2680 // does not need encoding 2681 } else { 2682 n ~= s[previous .. idx]; 2683 n ~= encodeChar(ch); 2684 previous = idx + 1; 2685 } 2686 } 2687 2688 if(n.length) { 2689 n ~= s[previous .. $]; 2690 return n; 2691 } else { 2692 return s; // nothing needed encoding 2693 } 2694 } 2695 unittest { 2696 assert(encodeUriComponent("foo") == "foo"); 2697 assert(encodeUriComponent("f33Ao") == "f33Ao"); 2698 assert(encodeUriComponent("/") == "%2F"); 2699 assert(encodeUriComponent("/foo") == "%2Ffoo"); 2700 assert(encodeUriComponent("foo/") == "foo%2F"); 2701 assert(encodeUriComponent("foo/bar") == "foo%2Fbar"); 2702 assert(encodeUriComponent("foo/bar/") == "foo%2Fbar%2F"); 2703 } 2704 2705 // FIXME: I think if translatePlusToSpace we're supposed to do newline normalization too 2706 package string decodeUriComponent(string s, bool translatePlusToSpace = false) { 2707 int skipping = 0; 2708 size_t previous = 0; 2709 string n = null; 2710 foreach(idx, char ch; s) { 2711 if(skipping) { 2712 skipping--; 2713 continue; 2714 } 2715 2716 if(ch == '%') { 2717 int hexDecode(char c) { 2718 if(c >= 'A' && c <= 'F') 2719 return c - 'A' + 10; 2720 else if(c >= 'a' && c <= 'f') 2721 return c - 'a' + 10; 2722 else if(c >= '0' && c <= '9') 2723 return c - '0' + 0; 2724 else 2725 throw ArsdException!"Invalid percent-encoding"("Invalid char encountered", idx, s); 2726 } 2727 2728 skipping = 2; 2729 n ~= s[previous .. idx]; 2730 2731 if(idx + 2 >= s.length) 2732 throw ArsdException!"Invalid percent-encoding"("End of string reached", idx, s); 2733 2734 n ~= (hexDecode(s[idx + 1]) << 4) | hexDecode(s[idx + 2]); 2735 2736 previous = idx + 3; 2737 } else if(translatePlusToSpace && ch == '+') { 2738 n ~= s[previous .. idx]; 2739 n ~= " "; 2740 previous = idx + 1; 2741 } 2742 } 2743 2744 if(n.length) { 2745 n ~= s[previous .. $]; 2746 return n; 2747 } else { 2748 return s; // nothing needed decoding 2749 } 2750 } 2751 2752 unittest { 2753 assert(decodeUriComponent("foo") == "foo"); 2754 assert(decodeUriComponent("%2F") == "/"); 2755 assert(decodeUriComponent("%2f") == "/"); 2756 assert(decodeUriComponent("%2Ffoo") == "/foo"); 2757 assert(decodeUriComponent("foo%2F") == "foo/"); 2758 assert(decodeUriComponent("foo%2Fbar") == "foo/bar"); 2759 assert(decodeUriComponent("foo%2Fbar%2F") == "foo/bar/"); 2760 assert(decodeUriComponent("%2F%2F%2F") == "///"); 2761 2762 assert(decodeUriComponent("+") == "+"); 2763 assert(decodeUriComponent("+", true) == " "); 2764 } 2765 2766 public auto toDelegate(T)(T t) { 2767 // static assert(is(T == function)); // lol idk how to do what i actually want here 2768 2769 static if(is(T Return == return)) 2770 static if(is(typeof(*T) Params == __parameters)) { 2771 static struct Wrapper { 2772 Return call(Params params) { 2773 return (cast(T) &this)(params); 2774 } 2775 } 2776 return &((cast(Wrapper*) t).call); 2777 } else static assert(0, "could not get params; is it already a delegate you can pass directly?"); 2778 else static assert(0, "could not get return value, if it is a functor maybe try getting a delegate with `&yourobj.opCall` instead of toDelegate(yourobj)"); 2779 } 2780 2781 @system unittest { 2782 int function(int) fn; 2783 fn = (a) { return a; }; 2784 2785 int delegate(int) dg = toDelegate(fn); 2786 2787 assert(dg.ptr is fn); // it stores the original function as the context pointer 2788 assert(dg.funcptr !is fn); // which is called through a lil trampoline 2789 assert(dg(5) == 5); // and forwards the args correctly 2790 } 2791 2792 /++ 2793 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. 2794 2795 It is intended for collecting a record of relevant UDAs off a symbol in a single call like this: 2796 2797 --- 2798 struct Name { 2799 string n; 2800 } 2801 2802 struct Validator { 2803 string regex; 2804 } 2805 2806 struct FormInfo { 2807 Name name; 2808 Validator validator; 2809 } 2810 2811 @Name("foo") @Validator(".*") 2812 void foo() {} 2813 2814 auto info = populateFromUdas!(FormInfo, __traits(getAttributes, foo)); 2815 assert(info.name == Name("foo")); 2816 assert(info.validator == Validator(".*")); 2817 --- 2818 2819 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: 2820 2821 --- 2822 void foo(T...)(T t) { 2823 auto info = populateFromArgs!(FormInfo)(t); 2824 // assuming the call below 2825 assert(info.name == Name("foo")); 2826 assert(info.validator == Validator(".*")); 2827 } 2828 2829 foo(Name("foo"), Validator(".*")); 2830 --- 2831 2832 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. 2833 +/ 2834 template populateFromUdas(Struct, UDAs...) { 2835 enum Struct populateFromUdas = () { 2836 Struct ret; 2837 foreach(memberName; __traits(allMembers, Struct)) { 2838 alias memberType = typeof(__traits(getMember, Struct, memberName)); 2839 foreach(uda; UDAs) { 2840 static if(is(memberType == PresenceOf!a, a)) { 2841 static if(__traits(isSame, a, uda)) 2842 __traits(getMember, ret, memberName) = true; 2843 } 2844 else 2845 static if(is(typeof(uda) : memberType)) { 2846 __traits(getMember, ret, memberName) = uda; 2847 } 2848 } 2849 } 2850 2851 return ret; 2852 }(); 2853 } 2854 2855 /// ditto 2856 Struct populateFromArgs(Struct, Args...)(Args args) { 2857 Struct ret; 2858 foreach(memberName; __traits(allMembers, Struct)) { 2859 alias memberType = typeof(__traits(getMember, Struct, memberName)); 2860 foreach(arg; args) { 2861 static if(is(typeof(arg == memberType))) { 2862 __traits(getMember, ret, memberName) = arg; 2863 } 2864 } 2865 } 2866 2867 return ret; 2868 } 2869 2870 /// ditto 2871 struct PresenceOf(alias a) { 2872 bool there; 2873 alias there this; 2874 } 2875 2876 /// 2877 unittest { 2878 enum a; 2879 enum b; 2880 struct Name { string name; } 2881 struct Info { 2882 Name n; 2883 PresenceOf!a athere; 2884 PresenceOf!b bthere; 2885 int c; 2886 } 2887 2888 void test() @a @Name("test") {} 2889 2890 auto info = populateFromUdas!(Info, __traits(getAttributes, test)); 2891 assert(info.n == Name("test")); // but present ones are in there 2892 assert(info.athere == true); // non-values can be tested with PresenceOf!it, which works like a bool 2893 assert(info.bthere == false); 2894 assert(info.c == 0); // absent thing will keep the default value 2895 } 2896 2897 /++ 2898 Declares a delegate property with several setters to allow for handlers that don't care about the arguments. 2899 2900 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. 2901 +/ 2902 struct FlexibleDelegate(DelegateType) { 2903 // please note that Parameters and ReturnType are public now! 2904 static if(is(DelegateType FunctionType == delegate)) 2905 static if(is(FunctionType Parameters == __parameters)) 2906 static if(is(DelegateType ReturnType == return)) { 2907 2908 /++ 2909 Calls the currently set delegate. 2910 2911 Diagnostics: 2912 If the callback delegate has not been set, this may cause a null pointer dereference. 2913 +/ 2914 ReturnType opCall(Parameters args) { 2915 return dg(args); 2916 } 2917 2918 /++ 2919 Use `if(thing)` to check if the delegate is null or not. 2920 +/ 2921 bool opCast(T : bool)() { 2922 return dg !is null; 2923 } 2924 2925 /++ 2926 These opAssign overloads are what puts the flexibility in the flexible delegate. 2927 2928 Bugs: 2929 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. 2930 +/ 2931 void opAssign(DelegateType dg) { 2932 this.dg = dg; 2933 } 2934 2935 /// ditto 2936 void opAssign(ReturnType delegate() dg) { 2937 this.dg = (Parameters ignored) => dg(); 2938 } 2939 2940 /// ditto 2941 void opAssign(ReturnType function(Parameters params) dg) { 2942 this.dg = (Parameters params) => dg(params); 2943 } 2944 2945 /// ditto 2946 void opAssign(ReturnType function() dg) { 2947 this.dg = (Parameters ignored) => dg(); 2948 } 2949 2950 /// ditto 2951 void opAssign(typeof(null) explicitNull) { 2952 this.dg = null; 2953 } 2954 2955 private DelegateType dg; 2956 } 2957 else static assert(0, DelegateType.stringof ~ " failed return value check"); 2958 else static assert(0, DelegateType.stringof ~ " failed parameters check"); 2959 else static assert(0, DelegateType.stringof ~ " failed delegate check"); 2960 } 2961 2962 /++ 2963 2964 +/ 2965 unittest { 2966 // you don't have to put the arguments in a struct, but i recommend 2967 // you do as it is more future proof - you can add more info to the 2968 // struct without breaking user code that consumes it. 2969 struct MyEventArguments { 2970 2971 } 2972 2973 // then you declare it just adding FlexibleDelegate!() around the 2974 // plain delegate type you'd normally use 2975 FlexibleDelegate!(void delegate(MyEventArguments args)) callback; 2976 2977 // until you set it, it will be null and thus be false in any boolean check 2978 assert(!callback); 2979 2980 // can set it to the properly typed thing 2981 callback = delegate(MyEventArguments args) {}; 2982 2983 // and now it is no longer null 2984 assert(callback); 2985 2986 // or if you don't care about the args, you can leave them off 2987 callback = () {}; 2988 2989 // and it works if the compiler types you as a function instead of delegate too 2990 // (which happens automatically if you don't access any local state or if you 2991 // explicitly define it as a function) 2992 2993 callback = function(MyEventArguments args) { }; 2994 2995 // can set it back to null explicitly if you ever wanted 2996 callback = null; 2997 2998 // the reflection info used internally also happens to be exposed publicly 2999 // which can actually sometimes be nice so if the language changes, i'll change 3000 // the code to keep this working. 3001 static assert(is(callback.ReturnType == void)); 3002 3003 // which can be convenient if the params is an annoying type since you can 3004 // consistently use something like this too 3005 callback = (callback.Parameters params) {}; 3006 3007 // check for null and call it pretty normally 3008 if(callback) 3009 callback(MyEventArguments()); 3010 } 3011 3012 /+ 3013 ====================== 3014 ERROR HANDLING HELPERS 3015 ====================== 3016 +/ 3017 3018 /+ + 3019 arsd code shouldn't be using Exception. Really, I don't think any code should be - instead, construct an appropriate object with structured information. 3020 3021 If you want to catch someone else's Exception, use `catch(object.Exception e)`. 3022 +/ 3023 //package deprecated struct Exception {} 3024 3025 3026 /++ 3027 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. 3028 3029 3030 $(H3 General guidelines for exceptions) 3031 3032 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. 3033 3034 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. 3035 3036 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. 3037 3038 With this in mind, here's some guidelines for exception handling in arsd code. 3039 3040 $(H4 Allocations and lifetimes) 3041 3042 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. 3043 3044 Keep your memory management schemes simple and let the garbage collector do its job. 3045 3046 $(LIST 3047 * All thrown exceptions should be allocated with the `new` keyword. 3048 3049 * Members inside the exception should be value types or have infinite lifetime (that is, be GC managed). 3050 3051 * 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. 3052 3053 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. 3054 ) 3055 3056 $(H4 Error strings) 3057 3058 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. 3059 3060 As such, I recommend that you: 3061 3062 $(LIST 3063 * 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. 3064 3065 * 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. 3066 3067 * $(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. 3068 ) 3069 3070 $(H4 Subclasses) 3071 3072 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. 3073 3074 See: [ArsdException], [Win32Enforce] 3075 3076 +/ 3077 class ArsdExceptionBase : object.Exception { 3078 /++ 3079 Don't call this except from other exceptions; this is essentially an abstract class. 3080 3081 Params: 3082 operation = the specific operation that failed, throwing the exception 3083 +/ 3084 package this(string operation, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3085 super(operation, file, line, next); 3086 } 3087 3088 /++ 3089 The toString method will print out several components: 3090 3091 $(LIST 3092 * 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]. 3093 * The generic category codes stored with this exception 3094 * Additional members stored with the exception child classes (e.g. platform error codes, associated function arguments) 3095 * The stack trace associated with the exception. You can access these lines independently with `foreach` over the `info` member. 3096 ) 3097 3098 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. 3099 +/ 3100 final override void toString(scope void delegate(in char[]) sink) const { 3101 // class name and info from constructor 3102 sink(printableExceptionName); 3103 sink("@"); 3104 sink(file); 3105 sink("("); 3106 char[16] buffer; 3107 sink(intToString(line, buffer[])); 3108 sink("): "); 3109 sink(message); 3110 3111 getAdditionalPrintableInformation((string name, in char[] value) { 3112 sink("\n"); 3113 sink(name); 3114 sink(": "); 3115 sink(value); 3116 }); 3117 3118 // full stack trace, if available 3119 if(info) { 3120 sink("\n----------------\n"); 3121 foreach(str; info) { 3122 sink(str); 3123 sink("\n"); 3124 } 3125 } 3126 } 3127 /// ditto 3128 final override string toString() { 3129 string s; 3130 toString((in char[] chunk) { s ~= chunk; }); 3131 return s; 3132 } 3133 3134 /++ 3135 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. 3136 3137 Overrides should always call `super.getAdditionalPrintableInformation(sink);` before adding additional information by calling the sink with other arguments afterward. 3138 3139 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. 3140 +/ 3141 void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 3142 3143 } 3144 3145 /++ 3146 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. 3147 +/ 3148 string printableExceptionName() const { 3149 return typeid(this).name; 3150 } 3151 3152 /// deliberately hiding `Throwable.msg`. Use [message] and [toString] instead. 3153 @disable final void msg() {} 3154 3155 override const(char)[] message() const { 3156 return super.msg; 3157 } 3158 } 3159 3160 /++ 3161 3162 +/ 3163 class InvalidArgumentsException : ArsdExceptionBase { 3164 static struct InvalidArgument { 3165 string name; 3166 string description; 3167 LimitedVariant givenValue; 3168 } 3169 3170 InvalidArgument[] invalidArguments; 3171 3172 this(InvalidArgument[] invalidArguments, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3173 this.invalidArguments = invalidArguments; 3174 super(functionName, file, line, next); 3175 } 3176 3177 this(string argumentName, string argumentDescription, LimitedVariant givenArgumentValue = LimitedVariant.init, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3178 this([ 3179 InvalidArgument(argumentName, argumentDescription, givenArgumentValue) 3180 ], functionName, file, line, next); 3181 } 3182 3183 this(string argumentName, string argumentDescription, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3184 this(argumentName, argumentDescription, LimitedVariant.init, functionName, file, line, next); 3185 } 3186 3187 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 3188 // FIXME: print the details better 3189 foreach(arg; invalidArguments) 3190 sink(arg.name, arg.givenValue.toString ~ " - " ~ arg.description); 3191 } 3192 } 3193 3194 /++ 3195 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. 3196 +/ 3197 class FeatureUnavailableException : ArsdExceptionBase { 3198 this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3199 super(featureName, file, line, next); 3200 } 3201 } 3202 3203 /++ 3204 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. 3205 +/ 3206 class NotYetImplementedException : FeatureUnavailableException { 3207 this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3208 super(featureName, file, line, next); 3209 } 3210 3211 } 3212 3213 /++ 3214 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. 3215 +/ 3216 class NotSupportedException : FeatureUnavailableException { 3217 this(string featureName, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3218 super(featureName, file, line, next); 3219 } 3220 } 3221 3222 /++ 3223 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. 3224 3225 You can catch an ArsdException to get its passed arguments out. 3226 3227 You can pass either a base class or a string as `Type`. 3228 3229 See the examples for how to use it. 3230 +/ 3231 template ArsdException(alias Type, DataTuple...) { 3232 static if(DataTuple.length) 3233 alias Parent = ArsdException!(Type, DataTuple[0 .. $-1]); 3234 else 3235 alias Parent = ArsdExceptionBase; 3236 3237 class ArsdException : Parent { 3238 DataTuple data; 3239 3240 this(DataTuple data, string file = __FILE__, size_t line = __LINE__) { 3241 this.data = data; 3242 static if(is(Parent == ArsdExceptionBase)) 3243 super(null, file, line); 3244 else 3245 super(data[0 .. $-1], file, line); 3246 } 3247 3248 static opCall(R...)(R r, string file = __FILE__, size_t line = __LINE__) { 3249 return new ArsdException!(Type, DataTuple, R)(r, file, line); 3250 } 3251 3252 override string printableExceptionName() const { 3253 static if(DataTuple.length) 3254 enum str = "ArsdException!(" ~ Type.stringof ~ ", " ~ DataTuple.stringof[1 .. $-1] ~ ")"; 3255 else 3256 enum str = "ArsdException!" ~ Type.stringof; 3257 return str; 3258 } 3259 3260 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 3261 ArsdExceptionBase.getAdditionalPrintableInformation(sink); 3262 3263 foreach(idx, datum; data) { 3264 enum int lol = cast(int) idx; 3265 enum key = "[" ~ lol.stringof ~ "] " ~ DataTuple[idx].stringof; 3266 sink(key, toStringInternal(datum)); 3267 } 3268 } 3269 } 3270 } 3271 3272 /// This example shows how you can throw and catch the ad-hoc exception types. 3273 unittest { 3274 // you can throw and catch by matching the string and argument types 3275 try { 3276 // throw it with parenthesis after the template args (it uses opCall to construct) 3277 throw ArsdException!"Test"(); 3278 // you could also `throw new ArsdException!"test";`, but that gets harder with args 3279 // as we'll see in the following example 3280 assert(0); // remove from docs 3281 } catch(ArsdException!"Test" e) { // catch it without them 3282 // this has no useful information except for the type 3283 // but you can catch it like this and it is still more than generic Exception 3284 } 3285 3286 // an exception's job is to deliver useful information up the chain 3287 // and you can do that easily by passing arguments: 3288 3289 try { 3290 throw ArsdException!"Test"(4, "four"); 3291 // you could also `throw new ArsdException!("Test", int, string)(4, "four")` 3292 // but now you start to see how the opCall convenience constructor simplifies things 3293 assert(0); // remove from docs 3294 } catch(ArsdException!("Test", int, string) e) { // catch it and use info by specifying types 3295 assert(e.data[0] == 4); // and extract arguments like this 3296 assert(e.data[1] == "four"); 3297 } 3298 3299 // a throw site can add additional information without breaking code that catches just some 3300 // generally speaking, each additional argument creates a new subclass on top of the previous args 3301 // so you can cast 3302 3303 try { 3304 throw ArsdException!"Test"(4, "four", 9); 3305 assert(0); // remove from docs 3306 } catch(ArsdException!("Test", int, string) e) { // this catch still works 3307 assert(e.data[0] == 4); 3308 assert(e.data[1] == "four"); 3309 // but if you were to print it, all the members would be there 3310 // import std.stdio; writeln(e); // would show something like: 3311 /+ 3312 ArsdException!("Test", int, string, int)@file.d(line): 3313 [0] int: 4 3314 [1] string: four 3315 [2] int: 9 3316 +/ 3317 // indicating that there's additional information available if you wanted to process it 3318 3319 // and meanwhile: 3320 ArsdException!("Test", int) e2 = e; // this implicit cast works thanks to the parent-child relationship 3321 ArsdException!"Test" e3 = e; // this works too, the base type/string still matches 3322 3323 // so catching those types would work too 3324 } 3325 } 3326 3327 /++ 3328 A tagged union that holds an error code from system apis, meaning one from Windows GetLastError() or C's errno. 3329 3330 You construct it with `SystemErrorCode(thing)` and the overloaded constructor tags and stores it. 3331 +/ 3332 struct SystemErrorCode { 3333 /// 3334 enum Type { 3335 errno, /// 3336 win32 /// 3337 } 3338 3339 const Type type; /// 3340 const int code; /// You should technically cast it back to DWORD if it is a win32 code 3341 3342 /++ 3343 C/unix error are typed as signed ints... 3344 Windows' errors are typed DWORD, aka unsigned... 3345 3346 so just passing them straight up will pick the right overload here to set the tag. 3347 +/ 3348 this(int errno) { 3349 this.type = Type.errno; 3350 this.code = errno; 3351 } 3352 3353 /// ditto 3354 this(uint win32) { 3355 this.type = Type.win32; 3356 this.code = win32; 3357 } 3358 3359 /++ 3360 Returns if the code indicated success. 3361 3362 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`. 3363 +/ 3364 bool wasSuccessful() const { 3365 final switch(type) { 3366 case Type.errno: 3367 return this.code == 0; 3368 case Type.win32: 3369 return this.code == 0; 3370 } 3371 } 3372 3373 /++ 3374 Constructs a string containing both the code and the explanation string. 3375 +/ 3376 string toString() const { 3377 return "[" ~ codeAsString ~ "] " ~ errorString; 3378 } 3379 3380 /++ 3381 The numeric code itself as a string. 3382 3383 See [errorString] for a text explanation of the code. 3384 +/ 3385 string codeAsString() const { 3386 char[16] buffer; 3387 final switch(type) { 3388 case Type.errno: 3389 return intToString(code, buffer[]).idup; 3390 case Type.win32: 3391 buffer[0 .. 2] = "0x"; 3392 return buffer[0 .. 2 + intToString(cast(uint) code, buffer[2 .. $], IntToStringArgs().withRadix(16).withPadding(8)).length].idup; 3393 } 3394 } 3395 3396 /++ 3397 A text explanation of the code. See [codeAsString] for a string representation of the numeric representation. 3398 +/ 3399 string errorString() const @trusted { 3400 final switch(type) { 3401 case Type.errno: 3402 import core.stdc.string; 3403 auto strptr = strerror(code); 3404 auto orig = strptr; 3405 int len; 3406 while(*strptr++) { 3407 len++; 3408 } 3409 3410 return orig[0 .. len].idup; 3411 case Type.win32: 3412 version(Windows) { 3413 wchar[256] buffer; 3414 auto size = FormatMessageW( 3415 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 3416 null, 3417 code, 3418 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 3419 buffer.ptr, 3420 buffer.length, 3421 null 3422 ); 3423 3424 return makeUtf8StringFromWindowsString(buffer[0 .. size]).stripInternal; 3425 } else { 3426 return null; 3427 } 3428 } 3429 } 3430 } 3431 3432 /++ 3433 3434 +/ 3435 struct SavedArgument { 3436 string name; 3437 LimitedVariant value; 3438 } 3439 3440 /++ 3441 3442 +/ 3443 class SystemApiException : ArsdExceptionBase { 3444 this(string msg, int originalErrorNo, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3445 this(msg, SystemErrorCode(originalErrorNo), args, file, line, next); 3446 } 3447 3448 version(Windows) 3449 this(string msg, DWORD windowsError, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3450 this(msg, SystemErrorCode(windowsError), args, file, line, next); 3451 } 3452 3453 this(string msg, SystemErrorCode code, SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3454 this.errorCode = code; 3455 3456 // discard stuff that won't fit 3457 if(args.length > this.args.length) 3458 args = args[0 .. this.args.length]; 3459 3460 this.args[0 .. args.length] = args[]; 3461 3462 super(msg, file, line, next); 3463 } 3464 3465 /++ 3466 3467 +/ 3468 const SystemErrorCode errorCode; 3469 3470 /++ 3471 3472 +/ 3473 const SavedArgument[8] args; 3474 3475 override void getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) const { 3476 super.getAdditionalPrintableInformation(sink); 3477 sink("Error code", errorCode.toString()); 3478 3479 foreach(arg; args) 3480 if(arg.name !is null) 3481 sink(arg.name, arg.value.toString()); 3482 } 3483 3484 } 3485 3486 /++ 3487 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]. 3488 3489 History: 3490 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 3491 +/ 3492 alias WindowsApiException = SystemApiException; 3493 3494 /++ 3495 History: 3496 Moved from simpledisplay.d to core.d in March 2023 (dub v11.0). 3497 +/ 3498 alias ErrnoApiException = SystemApiException; 3499 3500 /++ 3501 Calls the C API function `fn`. If it returns an error value, it throws an [ErrnoApiException] (or subclass) after getting `errno`. 3502 +/ 3503 template ErrnoEnforce(alias fn, alias errorValue = void) { 3504 static if(is(typeof(fn) Return == return)) 3505 static if(is(typeof(fn) Params == __parameters)) { 3506 static if(is(errorValue == void)) { 3507 static if(is(typeof(null) : Return)) 3508 enum errorValueToUse = null; 3509 else static if(is(Return : long)) 3510 enum errorValueToUse = -1; 3511 else 3512 static assert(0, "Please pass the error value"); 3513 } else { 3514 enum errorValueToUse = errorValue; 3515 } 3516 3517 Return ErrnoEnforce(Params params, ArgSentinel sentinel = ArgSentinel.init, string file = __FILE__, size_t line = __LINE__) { 3518 import core.stdc.errno; 3519 3520 Return value = fn(params); 3521 3522 if(value == errorValueToUse) { 3523 SavedArgument[] args; // FIXME 3524 /+ 3525 static foreach(idx; 0 .. Params.length) 3526 args ~= SavedArgument( 3527 __traits(identifier, Params[idx .. idx + 1]), 3528 params[idx] 3529 ); 3530 +/ 3531 throw new ErrnoApiException(__traits(identifier, fn), errno, args, file, line); 3532 } 3533 3534 return value; 3535 } 3536 } 3537 } 3538 3539 version(Windows) { 3540 /++ 3541 Calls the Windows API function `fn`. If it returns an error value, it throws a [WindowsApiException] (or subclass) after calling `GetLastError()`. 3542 +/ 3543 template Win32Enforce(alias fn, alias errorValue = void) { 3544 static if(is(typeof(fn) Return == return)) 3545 static if(is(typeof(fn) Params == __parameters)) { 3546 static if(is(errorValue == void)) { 3547 static if(is(Return == BOOL)) 3548 enum errorValueToUse = false; 3549 else static if(is(Return : HANDLE)) 3550 enum errorValueToUse = NULL; 3551 else static if(is(Return == DWORD)) 3552 enum errorValueToUse = cast(DWORD) 0xffffffff; 3553 else 3554 static assert(0, "Please pass the error value"); 3555 } else { 3556 enum errorValueToUse = errorValue; 3557 } 3558 3559 Return Win32Enforce(Params params, ArgSentinel sentinel = ArgSentinel.init, string file = __FILE__, size_t line = __LINE__) { 3560 Return value = fn(params); 3561 3562 if(value == errorValueToUse) { 3563 auto error = GetLastError(); 3564 SavedArgument[] args; // FIXME 3565 throw new WindowsApiException(__traits(identifier, fn), error, args, file, line); 3566 } 3567 3568 return value; 3569 } 3570 } 3571 } 3572 3573 } 3574 3575 /+ 3576 =============== 3577 EVENT LOOP CORE 3578 =============== 3579 +/ 3580 3581 /+ 3582 UI threads 3583 need to get window messages in addition to all the other jobs 3584 I/O Worker threads 3585 need to get commands for read/writes, run them, and send the reply back. not necessary on Windows 3586 if interrupted, check cancel flags. 3587 CPU Worker threads 3588 gets functions, runs them, send reply back. should send a cancel flag to periodically check 3589 Task worker threads 3590 runs fibers and multiplexes them 3591 3592 3593 General procedure: 3594 issue the read/write command 3595 if it would block on linux, epoll associate it. otherwise do the callback immediately 3596 3597 callbacks have default affinity to the current thread, meaning their callbacks always run here 3598 accepts can usually be dispatched to any available thread tho 3599 3600 // In other words, a single thread can be associated with, at most, one I/O completion port. 3601 3602 Realistically, IOCP only used if there is no thread affinity. If there is, just do overlapped w/ sleepex. 3603 3604 3605 case study: http server 3606 3607 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) 3608 2) connections come in and are assigned to first available thread via the iocp/global epoll 3609 3) these run local event loops until the connection task is finished 3610 3611 EVENT LOOP TYPES: 3612 1) main ui thread - MsgWaitForMultipleObjectsEx / epoll on the local ui. it does NOT check the any worker thread thing! 3613 The main ui thread should never terminate until the program is ready to close. 3614 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) 3615 3616 The biggest complication is the TerminalDirectToEmulator, where the primary ui thread is NOT the thread that runs `main` 3617 2) worker thread GetQueuedCompletionStatusEx / epoll on the local thread fd and the global epoll fd 3618 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. 3619 3620 i'll use: 3621 * QueueUserAPC to send interruptions to a worker thread 3622 * PostQueuedCompletionStatus is to send interruptions to any available thread. 3623 * PostMessage to a window 3624 * ??? to a fiber task 3625 3626 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) 3627 3628 Destructors need to be able to post messages back to a specific task to queue thread-affinity cleanup. This must be GC safe. 3629 3630 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. 3631 3632 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. 3633 +/ 3634 3635 /++ 3636 You can also pass a handle to a specific thread, if you have one. 3637 +/ 3638 enum ThreadToRunIn { 3639 /++ 3640 The callback should be only run by the same thread that set it. 3641 +/ 3642 CurrentThread, 3643 /++ 3644 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? 3645 3646 A ui thread should be always quickly responsive to new events. 3647 3648 There should only be one main ui thread, in which simpledisplay and minigui can be used. 3649 3650 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 3651 for an undeclared thread but will not receive messages from other threads unless there is no other option) 3652 3653 3654 Ad-Hoc thread - something running an event loop that isn't another thing 3655 Controller thread - running an explicit event loop instance set as not a task runner or blocking worker 3656 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) 3657 3658 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. 3659 3660 All use the MsgWaitForMultipleObjectsEx pattern 3661 3662 3663 +/ 3664 UiThread, 3665 /++ 3666 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. 3667 3668 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). 3669 3670 These are expected to run cooperatively multitasked things; functions that frequently yield as they wait on other tasks. Think a fiber. 3671 3672 A task runner should be generally responsive to new events. 3673 +/ 3674 AnyAvailableTaskRunnerThread, 3675 /++ 3676 These are expected to run longer blocking, but independent operations. Think an individual function with no context. 3677 3678 A blocking worker can wait hundreds of milliseconds between checking for new events. 3679 +/ 3680 AnyAvailableBlockingWorkerThread, 3681 /++ 3682 The callback will be duplicated across all threads known to the arsd.core event loop. 3683 3684 It adds it to an immutable queue that each thread will go through... might just replace with an exit() function. 3685 3686 3687 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. 3688 3689 threads should report when they start running the loop and they really should report when they terminate but that isn't reliable 3690 3691 3692 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. 3693 3694 there's still prolly be one "the" ui thread, which does the handle listening on windows and is the one sdpy wants. 3695 +/ 3696 BroadcastToAllThreads, 3697 } 3698 3699 /++ 3700 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. 3701 +/ 3702 void arsd_core_init(int numberOfWorkers = 0) { 3703 3704 } 3705 3706 version(Windows) 3707 class WindowsHandleReader_ex { 3708 // 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 3709 this(HANDLE handle) {} 3710 } 3711 3712 version(Posix) 3713 class PosixFdReader_ex { 3714 // posix readers can just register with whatever instance we want to handle the callback 3715 } 3716 3717 /++ 3718 3719 +/ 3720 interface ICoreEventLoop { 3721 /++ 3722 Runs the event loop for this thread until the `until` delegate returns `true`. 3723 +/ 3724 final void run(scope bool delegate() until) { 3725 exitApplicationRequested = false; 3726 while(!exitApplicationRequested && !until()) { 3727 runOnce(); 3728 } 3729 } 3730 3731 package static int function() getTimeout; 3732 private __gshared bool exitApplicationRequested; 3733 3734 final static void exitApplication() { 3735 exitApplicationRequested = true; 3736 // FIXME: wake up all the threads 3737 } 3738 3739 /++ 3740 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`. 3741 3742 History: 3743 Added December 28, 2023 3744 +/ 3745 static struct RunOnceResult { 3746 enum Possibilities { 3747 CarryOn, 3748 LocalExit, 3749 GlobalExit, 3750 Interrupted 3751 3752 } 3753 Possibilities result; 3754 3755 /++ 3756 Returns `true` if the event loop should generally continue. 3757 3758 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. 3759 +/ 3760 bool shouldContinue() const { 3761 return result == Possibilities.CarryOn; 3762 } 3763 3764 /++ 3765 Returns `true` if [ICoreEventLoop.exitApplication] was called during this event, or if the user or operating system has requested the application exit. 3766 3767 Details might be available through other means. 3768 +/ 3769 bool applicationExitRequested() const { 3770 return result == Possibilities.GlobalExit; 3771 } 3772 3773 /++ 3774 Returns [shouldContinue] when used in a context for an implicit bool (e.g. `if` statements). 3775 +/ 3776 bool opCast(T : bool)() const { 3777 reutrn shouldContinue(); 3778 } 3779 } 3780 3781 /++ 3782 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. 3783 3784 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. 3785 3786 Parameters: 3787 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). 3788 3789 History: 3790 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. 3791 +/ 3792 RunOnceResult runOnce(Duration timeout = Duration.max); 3793 3794 /++ 3795 Adds a delegate to be called on each loop iteration, called based on the `timingFlags`. 3796 3797 3798 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. 3799 3800 Parameters: 3801 dg = the delegate to call 3802 timingFlags = 3803 0: never actually run the function; it can assert error if you pass this 3804 1: run before each loop OS wait call 3805 2: run after each loop OS wait call 3806 3: run both before and after each OS wait call 3807 4: single shot? NOT IMPLEMENTED 3808 8: no-coalesce? NOT IMPLEMENTED (if after was just run, it will skip the before loops unless this flag is set) 3809 3810 FIXME: it should return a handle you can use to unregister it 3811 +/ 3812 UnregisterToken addDelegateOnLoopIteration(void delegate() dg, uint timingFlags); 3813 3814 final UnregisterToken addDelegateOnLoopIteration(void function() dg, uint timingFlags) { 3815 if(timingFlags == 0) 3816 assert(0, "would never run"); 3817 return addDelegateOnLoopIteration(toDelegate(dg), timingFlags); 3818 } 3819 3820 // to send messages between threads, i'll queue up a function that just call dispatchMessage. can embed the arg inside the callback helper prolly. 3821 // 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 3822 3823 version(Posix) { 3824 @mustuse 3825 static struct UnregisterToken { 3826 private CoreEventLoopImplementation impl; 3827 private int fd = -1; 3828 private CallbackHelper cb; 3829 private void delegate() dg; 3830 3831 /++ 3832 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). 3833 3834 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). 3835 +/ 3836 void unregister() { 3837 assert(impl !is null, "Cannot reuse unregister token"); 3838 3839 if(dg !is null) 3840 impl.unregisterDg(dg); 3841 3842 version(Arsd_core_epoll) { 3843 if(fd != -1) 3844 impl.unregisterFd(fd); 3845 } else version(Arsd_core_dispatch) { 3846 throw new NotYetImplementedException(); 3847 } else version(Arsd_core_kqueue) { 3848 // intentionally blank - all registrations are one-shot there 3849 // FIXME: actually it might not have gone off yet, in that case we do need to delete the filter 3850 } else version(EmptyCoreEvent) { 3851 3852 } 3853 else static assert(0); 3854 3855 if(cb) 3856 cb.release(); 3857 this = typeof(this).init; 3858 } 3859 } 3860 3861 @mustuse 3862 static struct RearmToken { 3863 private bool readable; 3864 private CoreEventLoopImplementation impl; 3865 private int fd; 3866 private CallbackHelper cb; 3867 private uint flags; 3868 3869 /++ 3870 Calls [UnregisterToken.unregister] 3871 +/ 3872 void unregister() { 3873 assert(impl !is null, "cannot reuse rearm token after unregistering it"); 3874 3875 version(Arsd_core_epoll) { 3876 impl.unregisterFd(fd); 3877 } else version(Arsd_core_dispatch) { 3878 throw new NotYetImplementedException(); 3879 } else version(Arsd_core_kqueue) { 3880 // intentionally blank - all registrations are one-shot there 3881 // FIXME: actually it might not have gone off yet, in that case we do need to delete the filter 3882 } else version(EmptyCoreEvent) { 3883 3884 } else static assert(0); 3885 3886 cb.release(); 3887 this = typeof(this).init; 3888 } 3889 3890 /++ 3891 Rearms the event so you will get another callback next time it is ready. 3892 +/ 3893 void rearm() { 3894 assert(impl !is null, "cannot reuse rearm token after unregistering it"); 3895 impl.rearmFd(this); 3896 } 3897 } 3898 3899 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb); 3900 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb); 3901 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb); 3902 } 3903 3904 version(Windows) { 3905 @mustuse 3906 static struct UnregisterToken { 3907 private CoreEventLoopImplementation impl; 3908 private HANDLE handle; 3909 private CallbackHelper cb; 3910 private void delegate() dg; 3911 3912 /++ 3913 Unregisters the handle from the event loop and releases the reference to the callback held by the event loop (which will probably free it). 3914 3915 You must call this when you're done. Normally, this will be right before you close the handle. 3916 +/ 3917 void unregister() { 3918 assert(impl !is null, "Cannot reuse unregister token"); 3919 3920 if(dg !is null) 3921 impl.unregisterDg(dg); 3922 3923 if(handle) 3924 impl.unregisterHandle(handle, cb); 3925 3926 if(cb) 3927 cb.release(); 3928 this = typeof(this).init; 3929 } 3930 } 3931 3932 UnregisterToken addCallbackOnHandleReady(HANDLE handle, CallbackHelper cb); 3933 } 3934 } 3935 3936 /++ 3937 Get the event loop associated with this thread 3938 +/ 3939 ICoreEventLoop getThisThreadEventLoop(EventLoopType type = EventLoopType.AdHoc) { 3940 static ICoreEventLoop loop; 3941 if(loop is null) 3942 loop = new CoreEventLoopImplementation(); 3943 return loop; 3944 } 3945 3946 /++ 3947 The internal types that will be exposed through other api things. 3948 +/ 3949 package(arsd) enum EventLoopType { 3950 /++ 3951 The event loop is being run temporarily and the thread doesn't promise to keep running it. 3952 +/ 3953 AdHoc, 3954 /++ 3955 The event loop struct has been instantiated at top level. Its destructor will run when the 3956 function exits, which is only at the end of the entire block of work it is responsible for. 3957 3958 It must be in scope for the whole time the arsd event loop functions are expected to be used 3959 (meaning it should generally be top-level in `main`) 3960 +/ 3961 Explicit, 3962 /++ 3963 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. 3964 +/ 3965 Ui, 3966 /++ 3967 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). 3968 +/ 3969 TaskRunner, 3970 /++ 3971 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. 3972 +/ 3973 HelperWorker 3974 } 3975 3976 /+ 3977 Tasks are given an object to talk to their parent... can be a dialog where it is like 3978 3979 sendBuffer 3980 waitForWordToProceed 3981 3982 in a loop 3983 3984 3985 Tasks are assigned to a worker thread and may share it with other tasks. 3986 +/ 3987 3988 /+ 3989 private ThreadLocalGcRoots gcRoots; 3990 3991 private struct ThreadLocalGcRoots { 3992 // it actually would be kinda cool if i could tell the GC 3993 // that only part of this array is actually used so it can skip 3994 // scanning the rest. but meh. 3995 const(void)*[] roots; 3996 3997 void* add(const(void)* what) { 3998 roots ~= what; 3999 return &roots[$-1]; 4000 } 4001 } 4002 +/ 4003 4004 // the GC may not be able to see this! remember, it can be hidden inside kernel buffers 4005 package(arsd) class CallbackHelper { 4006 import core.memory; 4007 4008 void call() { 4009 if(callback) 4010 callback(); 4011 } 4012 4013 void delegate() callback; 4014 void*[3] argsStore; 4015 4016 void addref() { 4017 version(HasThread) 4018 atomicOp!"+="(refcount, 1); 4019 } 4020 4021 void release() { 4022 version(HasThread) 4023 if(atomicOp!"-="(refcount, 1) <= 0) { 4024 if(flags & 1) 4025 GC.removeRoot(cast(void*) this); 4026 } 4027 } 4028 4029 private shared(int) refcount; 4030 private uint flags; 4031 4032 this(void function() callback) { 4033 this( () { callback(); } ); 4034 } 4035 4036 this(void delegate() callback, bool addRoot = true) { 4037 version(HasThread) 4038 if(addRoot) { 4039 GC.addRoot(cast(void*) this); 4040 this.flags |= 1; 4041 } 4042 4043 this.addref(); 4044 this.callback = callback; 4045 } 4046 } 4047 4048 inout(char)[] trimSlashesRight(inout(char)[] txt) { 4049 //if(txt.length && (txt[0] == '/' || txt[0] == '\\')) 4050 //txt = txt[1 .. $]; 4051 4052 if(txt.length && (txt[$-1] == '/' || txt[$-1] == '\\')) 4053 txt = txt[0 .. $-1]; 4054 4055 return txt; 4056 } 4057 4058 enum TreatAsWindowsPath { 4059 guess, 4060 ifVersionWindows, 4061 yes, 4062 no, 4063 } 4064 4065 // FIXME add uri from cgi/http2 and make sure the relative methods are reasonable compatible 4066 4067 /++ 4068 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. 4069 4070 This type is meant to represent a filename / path. I might not keep it around. 4071 +/ 4072 struct FilePath { 4073 private string path; 4074 4075 this(string path) { 4076 this.path = path; 4077 } 4078 4079 bool isNull() const { 4080 return path is null; 4081 } 4082 4083 bool opCast(T:bool)() const { 4084 return !isNull; 4085 } 4086 4087 string toString() const { 4088 return path; 4089 } 4090 4091 //alias toString this; 4092 4093 /+ +++++++++++++++++ +/ 4094 /+ String analysis +/ 4095 /+ +++++++++++++++++ +/ 4096 4097 FilePath makeAbsolute(FilePath base, TreatAsWindowsPath treatAsWindowsPath = TreatAsWindowsPath.guess) const { 4098 if(base.path.length == 0) 4099 return this.removeExtraParts(); 4100 if(base.path[$-1] != '/' && base.path[$-1] != '\\') 4101 base.path ~= '/'; 4102 4103 bool isWindowsPath; 4104 final switch(treatAsWindowsPath) { 4105 case TreatAsWindowsPath.guess: 4106 case TreatAsWindowsPath.yes: 4107 isWindowsPath = true; 4108 break; 4109 case TreatAsWindowsPath.no: 4110 isWindowsPath = false; 4111 break; 4112 case TreatAsWindowsPath.ifVersionWindows: 4113 version(Windows) 4114 isWindowsPath = true; 4115 else 4116 isWindowsPath = false; 4117 break; 4118 } 4119 if(isWindowsPath) { 4120 if(this.isUNC) 4121 return this.removeExtraParts(); 4122 if(this.driveName) 4123 return this.removeExtraParts(); 4124 if(this.path.length >= 1 && (this.path[0] == '/' || this.path[0] == '\\')) { 4125 // drive-relative path, take the drive from the base 4126 return FilePath(base.driveName ~ this.path).removeExtraParts(); 4127 } 4128 // otherwise, take the dir name from the base and add us onto it 4129 return FilePath(base.directoryName ~ this.path).removeExtraParts(); 4130 } else { 4131 if(this.path.length >= 1 && this.path[0] == '/') 4132 return this.removeExtraParts(); 4133 else 4134 return FilePath(base.directoryName ~ this.path).removeExtraParts(); 4135 } 4136 } 4137 4138 /+ 4139 FilePath makeRelative(FilePath base, TreatAsWindowsPath treatAsWindowsPath = TreatAsWindowsPath.guess) const { 4140 if(this.path.startsWith(base.path)) { 4141 auto p = this.path[base.path .. $]; 4142 if(p.length && p[0] == '/') 4143 p = p[1 .. $]; 4144 if(p.length) 4145 return FilePath(p); 4146 } 4147 throw new Exception("idk how to make " ~ this.path ~ " relative to " ~ base.path); 4148 } 4149 +/ 4150 4151 // dg returns true to continue, false to break 4152 void foreachPathComponent(scope bool delegate(size_t index, in char[] component) dg) const { 4153 size_t start; 4154 size_t skip; 4155 if(isUNC()) { 4156 dg(start, this.path[start .. 2]); 4157 start = 2; 4158 skip = 2; 4159 } 4160 foreach(idx, ch; this.path) { 4161 if(skip) { skip--; continue; } 4162 if(ch == '/' || ch == '\\') { 4163 if(!dg(start, this.path[start .. idx + 1])) 4164 return; 4165 start = idx + 1; 4166 } 4167 } 4168 if(start != path.length) 4169 dg(start, this.path[start .. $]); 4170 } 4171 4172 // remove cases of // or /. or /.. Only valid to call this on an absolute path. 4173 private FilePath removeExtraParts() const { 4174 bool changeNeeded; 4175 foreachPathComponent((idx, component) { 4176 auto name = component.trimSlashesRight; 4177 if(name.length == 0 && idx != 0) 4178 changeNeeded = true; 4179 if(name == "." || name == "..") 4180 changeNeeded = true; 4181 return !changeNeeded; 4182 }); 4183 4184 if(!changeNeeded) 4185 return this; 4186 4187 string newPath; 4188 foreachPathComponent((idx, component) { 4189 auto name = component.trimSlashesRight; 4190 if(component == `\\`) // must preserve unc paths 4191 newPath ~= component; 4192 else if(name.length == 0 && idx != 0) 4193 {} 4194 else if(name == ".") 4195 {} 4196 else if(name == "..") { 4197 // remove the previous component, unless it is the first component 4198 auto sofar = FilePath(newPath); 4199 size_t previousComponentIndex; 4200 sofar.foreachPathComponent((idx2, component2) { 4201 if(idx2 != newPath.length) 4202 previousComponentIndex = idx2; 4203 return true; 4204 }); 4205 4206 if(previousComponentIndex && previousComponentIndex != newPath.length) { 4207 newPath = newPath[0 .. previousComponentIndex]; 4208 //newPath.assumeSafeAppend(); 4209 } 4210 } else { 4211 newPath ~= component; 4212 } 4213 4214 return true; 4215 }); 4216 4217 return FilePath(newPath); 4218 } 4219 4220 // assuming we're looking at a Windows path... 4221 bool isUNC() const { 4222 return (path.length > 2 && path[0 .. 2] == `\\`); 4223 } 4224 4225 // assuming we're looking at a Windows path... 4226 string driveName() const { 4227 if(path.length < 2) 4228 return null; 4229 if((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) { 4230 if(path[1] == ':') { 4231 if(path.length == 2 || path[2] == '\\' || path[2] == '/') 4232 return path[0 .. 2]; 4233 } 4234 } 4235 return null; 4236 } 4237 4238 /+ 4239 bool isAbsolute() { 4240 if(path.length && path[0] == '/') 4241 return true; 4242 4243 } 4244 4245 FilePath relativeTo() { 4246 4247 } 4248 4249 bool matchesGlobPattern(string globPattern) { 4250 4251 } 4252 4253 this(string directoryName, string filename) {} 4254 this(string directoryName, string basename, string extension) {} 4255 4256 // remove ./, ../, stuff like that 4257 FilePath normalize(FilePath relativeTo) {} 4258 +/ 4259 4260 /++ 4261 Returns the path with the directory cut off. 4262 +/ 4263 string filename() { 4264 foreach_reverse(idx, ch; path) { 4265 if(ch == '\\' || ch == '/') 4266 return path[idx + 1 .. $]; 4267 } 4268 return path; 4269 } 4270 4271 /++ 4272 Returns the path with the filename cut off. 4273 +/ 4274 string directoryName() { 4275 auto fn = this.filename(); 4276 if(fn is path) 4277 return null; 4278 return path[0 .. $ - fn.length]; 4279 } 4280 4281 /++ 4282 Returns the file extension, if present, including the last dot. 4283 +/ 4284 string extension() { 4285 foreach_reverse(idx, ch; path) { 4286 if(ch == '.') 4287 return path[idx .. $]; 4288 } 4289 return null; 4290 } 4291 4292 /++ 4293 Guesses the media (aka mime) content type from the file extension for this path. 4294 4295 Only has a few things supported. Returns null if it doesn't know. 4296 4297 History: 4298 Moved from arsd.cgi to arsd.core.FilePath on October 28, 2024 4299 +/ 4300 string contentTypeFromFileExtension() { 4301 switch(this.extension) { 4302 // images 4303 case ".png": 4304 return "image/png"; 4305 case ".apng": 4306 return "image/apng"; 4307 case ".svg": 4308 return "image/svg+xml"; 4309 case ".jpg": 4310 case ".jpeg": 4311 return "image/jpeg"; 4312 4313 case ".txt": 4314 return "text/plain"; 4315 4316 case ".html": 4317 return "text/html"; 4318 case ".css": 4319 return "text/css"; 4320 case ".js": 4321 return "application/javascript"; 4322 case ".wasm": 4323 return "application/wasm"; 4324 4325 case ".mp3": 4326 return "audio/mpeg"; 4327 4328 case ".pdf": 4329 return "application/pdf"; 4330 4331 default: 4332 return null; 4333 } 4334 } 4335 } 4336 4337 unittest { 4338 FilePath fn; 4339 4340 fn = FilePath("dir/name.ext"); 4341 assert(fn.directoryName == "dir/"); 4342 assert(fn.filename == "name.ext"); 4343 assert(fn.extension == ".ext"); 4344 4345 fn = FilePath(null); 4346 assert(fn.directoryName is null); 4347 assert(fn.filename is null); 4348 assert(fn.extension is null); 4349 4350 fn = FilePath("file.txt"); 4351 assert(fn.directoryName is null); 4352 assert(fn.filename == "file.txt"); 4353 assert(fn.extension == ".txt"); 4354 4355 fn = FilePath("dir/"); 4356 assert(fn.directoryName == "dir/"); 4357 assert(fn.filename == ""); 4358 assert(fn.extension is null); 4359 4360 assert(fn.makeAbsolute(FilePath("/")).path == "/dir/"); 4361 assert(fn.makeAbsolute(FilePath("file.txt")).path == "file.txt/dir/"); // FilePaths as a base are ALWAYS treated as a directory 4362 assert(FilePath("file.txt").makeAbsolute(fn).path == "dir/file.txt"); 4363 4364 assert(FilePath("c:/file.txt").makeAbsolute(FilePath("d:/")).path == "c:/file.txt"); 4365 assert(FilePath("../file.txt").makeAbsolute(FilePath("d:/")).path == "d:/file.txt"); 4366 4367 assert(FilePath("../file.txt").makeAbsolute(FilePath("d:/foo")).path == "d:/file.txt"); 4368 assert(FilePath("../file.txt").makeAbsolute(FilePath("d:/")).path == "d:/file.txt"); 4369 assert(FilePath("../file.txt").makeAbsolute(FilePath("/home/me")).path == "/home/file.txt"); 4370 assert(FilePath("../file.txt").makeAbsolute(FilePath(`\\arsd\me`)).path == `\\arsd\file.txt`); 4371 assert(FilePath("../../file.txt").makeAbsolute(FilePath("/home/me")).path == "/file.txt"); 4372 assert(FilePath("../../../file.txt").makeAbsolute(FilePath("/home/me")).path == "/file.txt"); 4373 4374 assert(FilePath("test/").makeAbsolute(FilePath("/home/me/")).path == "/home/me/test/"); 4375 assert(FilePath("/home/me/test/").makeAbsolute(FilePath("/home/me/test/")).path == "/home/me/test/"); 4376 } 4377 4378 version(HasFile) 4379 /++ 4380 History: 4381 Added January 2, 2024 4382 +/ 4383 FilePath getCurrentWorkingDirectory() { 4384 version(Windows) { 4385 wchar[256] staticBuffer; 4386 wchar[] buffer = staticBuffer[]; 4387 4388 try_again: 4389 auto ret = GetCurrentDirectoryW(cast(DWORD) buffer.length, buffer.ptr); 4390 if(ret == 0) 4391 throw new WindowsApiException("GetCurrentDirectoryW", GetLastError()); 4392 if(ret < buffer.length) { 4393 return FilePath(makeUtf8StringFromWindowsString(buffer[0 .. ret])); 4394 } else { 4395 buffer.length = ret; 4396 goto try_again; 4397 } 4398 } else version(Posix) { 4399 char[128] staticBuffer; 4400 char[] buffer = staticBuffer[]; 4401 4402 try_again: 4403 auto ret = getcwd(buffer.ptr, buffer.length); 4404 if(ret is null && errno == ERANGE && buffer.length < 4096 / 2) { 4405 buffer.length = buffer.length * 2; 4406 goto try_again; 4407 } else if(ret is null) { 4408 throw new ErrnoApiException("getcwd", errno); 4409 } 4410 return FilePath(stringz(ret).borrow.idup); 4411 } else 4412 assert(0, "Not implemented"); 4413 } 4414 4415 /++ 4416 Specialization of `string` to indicate it is a URI. You should generally use [arsd.uri.Uri] instead of this in user code. 4417 4418 History: 4419 Added November 2, 2025 4420 +/ 4421 struct UriString { 4422 string uri; 4423 4424 alias toString this; 4425 4426 string toString() { 4427 return uri; 4428 } 4429 } 4430 4431 /++ 4432 Shared base code for web socket client in [arsd.http2] and server in [arsd.cgi]. 4433 4434 History: 4435 Moved to arsd.core on November 2, 2025 4436 +/ 4437 class WebSocketBase { 4438 /* copy/paste section { */ 4439 4440 package int readyState_; 4441 protected ubyte[] receiveBuffer; 4442 protected size_t receiveBufferUsedLength; 4443 4444 protected Config config; 4445 4446 enum CONNECTING = 0; /// Socket has been created. The connection is not yet open. 4447 enum OPEN = 1; /// The connection is open and ready to communicate. 4448 enum CLOSING = 2; /// The connection is in the process of closing. 4449 enum CLOSED = 3; /// The connection is closed or couldn't be opened. 4450 4451 /++ 4452 4453 +/ 4454 /// Group: foundational 4455 static struct Config { 4456 /++ 4457 These control the size of the receive buffer. 4458 4459 It starts at the initial size, will temporarily 4460 balloon up to the maximum size, and will reuse 4461 a buffer up to the likely size. 4462 4463 Anything larger than the maximum size will cause 4464 the connection to be aborted and an exception thrown. 4465 This is to protect you against a peer trying to 4466 exhaust your memory, while keeping the user-level 4467 processing simple. 4468 +/ 4469 size_t initialReceiveBufferSize = 4096; 4470 size_t likelyReceiveBufferSize = 4096; /// ditto 4471 size_t maximumReceiveBufferSize = 10 * 1024 * 1024; /// ditto 4472 4473 /++ 4474 Maximum combined size of a message. 4475 +/ 4476 size_t maximumMessageSize = 10 * 1024 * 1024; 4477 4478 string[string] cookies; /// Cookies to send with the initial request. cookies[name] = value; 4479 string origin; /// Origin URL to send with the handshake, if desired. 4480 string protocol; /// the protocol header, if desired. 4481 4482 /++ 4483 Additional headers to put in the HTTP request. These should be formatted `Name: value`, like for example: 4484 4485 --- 4486 Config config; 4487 config.additionalHeaders ~= "Authorization: Bearer your_auth_token_here"; 4488 --- 4489 4490 History: 4491 Added February 19, 2021 (included in dub version 9.2) 4492 +/ 4493 string[] additionalHeaders; 4494 4495 /++ 4496 Amount of time (in msecs) of idleness after which to send an automatic ping 4497 4498 Please note how this interacts with [timeoutFromInactivity] - a ping counts as activity that 4499 keeps the socket alive. 4500 +/ 4501 int pingFrequency = 5000; 4502 4503 /++ 4504 Amount of time to disconnect when there's no activity. Note that automatic pings will keep the connection alive; this timeout only occurs if there's absolutely nothing, including no responses to websocket ping frames. Since the default [pingFrequency] is only seconds, this one minute should never elapse unless the connection is actually dead. 4505 4506 The one thing to keep in mind is if your program is busy and doesn't check input, it might consider this a time out since there's no activity. The reason is that your program was busy rather than a connection failure, but it doesn't care. You should avoid long processing periods anyway though! 4507 4508 History: 4509 Added March 31, 2021 (included in dub version 9.4) 4510 +/ 4511 Duration timeoutFromInactivity = 1.minutes; 4512 4513 /++ 4514 For https connections, if this is `true`, it will fail to connect if the TLS certificate can not be 4515 verified. Setting this to `false` will skip this check and allow the connection to continue anyway. 4516 4517 History: 4518 Added April 5, 2022 (dub v10.8) 4519 4520 Prior to this, it always used the global (but undocumented) `defaultVerifyPeer` setting, and sometimes 4521 even if it was true, it would skip the verification. Now, it always respects this local setting. 4522 +/ 4523 bool verifyPeer = true; 4524 } 4525 4526 /++ 4527 Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED]. 4528 +/ 4529 int readyState() { 4530 return readyState_; 4531 } 4532 4533 /++ 4534 Closes the connection, sending a graceful teardown message to the other side. 4535 If you provide no arguments, it sends code 1000, normal closure. If you provide 4536 a code, you should also provide a short reason string. 4537 4538 Params: 4539 code = reason code. 4540 4541 0-999 are invalid. 4542 1000-2999 are defined by the RFC. [https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1] 4543 1000 - normal finish 4544 1001 - endpoint going away 4545 1002 - protocol error 4546 1003 - unacceptable data received (e.g. binary message when you can't handle it) 4547 1004 - reserved 4548 1005 - missing status code (should not be set except by implementations) 4549 1006 - abnormal connection closure (should only be set by implementations) 4550 1007 - inconsistent data received (i.e. utf-8 decode error in text message) 4551 1008 - policy violation 4552 1009 - received message too big 4553 1010 - client aborting due to required extension being unsupported by the server 4554 1011 - server had unexpected failure 4555 1015 - reserved for TLS handshake failure 4556 3000-3999 are to be registered with IANA. 4557 4000-4999 are private-use custom codes depending on the application. These are what you'd most commonly set here. 4558 4559 reason = <= 123 bytes of human-readable reason text, used for logs and debugging 4560 4561 History: 4562 The default `code` was changed to 1000 on January 9, 2023. Previously it was 0, 4563 but also ignored anyway. 4564 4565 On May 11, 2024, the optional arguments were changed to overloads since if you provide a code, you should also provide a reason. 4566 +/ 4567 /// Group: foundational 4568 void close() { 4569 close(1000, null); 4570 } 4571 4572 /// ditto 4573 void close(int code, string reason) 4574 //in (reason.length < 123) 4575 in { assert(reason.length <= 123); } do 4576 { 4577 if(readyState_ != OPEN) 4578 return; // it cool, we done 4579 WebSocketFrame wss; 4580 wss.fin = true; 4581 wss.masked = this.isClient; 4582 wss.opcode = WebSocketOpcode.close; 4583 wss.data = [ubyte((code >> 8) & 0xff), ubyte(code & 0xff)] ~ cast(ubyte[]) reason.dup; 4584 wss.send(&llsend, &getRandomByte); 4585 4586 readyState_ = CLOSING; 4587 4588 closeCalled = true; 4589 4590 llshutdown(); 4591 } 4592 4593 deprecated("If you provide a code, please also provide a reason string") void close(int code) { 4594 close(code, null); 4595 } 4596 4597 4598 protected bool closeCalled; 4599 4600 /++ 4601 Sends a ping message to the server. This is done automatically by the library if you set a non-zero [Config.pingFrequency], but you can also send extra pings explicitly as well with this function. 4602 +/ 4603 /// Group: foundational 4604 void ping(in ubyte[] data = null) { 4605 WebSocketFrame wss; 4606 wss.fin = true; 4607 wss.masked = this.isClient; 4608 wss.opcode = WebSocketOpcode.ping; 4609 if(data !is null) wss.data = data.dup; 4610 wss.send(&llsend, &getRandomByte); 4611 } 4612 4613 /++ 4614 Sends a pong message to the server. This is normally done automatically in response to pings. 4615 +/ 4616 /// Group: foundational 4617 void pong(in ubyte[] data = null) { 4618 WebSocketFrame wss; 4619 wss.fin = true; 4620 wss.masked = this.isClient; 4621 wss.opcode = WebSocketOpcode.pong; 4622 if(data !is null) wss.data = data.dup; 4623 wss.send(&llsend, &getRandomByte); 4624 } 4625 4626 /++ 4627 Sends a text message through the websocket. 4628 +/ 4629 /// Group: foundational 4630 void send(in char[] textData) { 4631 WebSocketFrame wss; 4632 wss.fin = true; 4633 wss.masked = this.isClient; 4634 wss.opcode = WebSocketOpcode.text; 4635 wss.data = cast(ubyte[]) textData.dup; 4636 wss.send(&llsend, &getRandomByte); 4637 } 4638 4639 /++ 4640 Sends a binary message through the websocket. 4641 +/ 4642 /// Group: foundational 4643 void send(in ubyte[] binaryData) { 4644 WebSocketFrame wss; 4645 wss.masked = this.isClient; 4646 wss.fin = true; 4647 wss.opcode = WebSocketOpcode.binary; 4648 wss.data = cast(ubyte[]) binaryData.dup; 4649 wss.send(&llsend, &getRandomByte); 4650 } 4651 4652 /++ 4653 Waits for and returns the next complete message on the socket. 4654 4655 Note that the onmessage function is still called, right before 4656 this returns. 4657 +/ 4658 /// Group: blocking_api 4659 public WebSocketFrame waitForNextMessage() { 4660 do { 4661 auto m = processOnce(); 4662 if(m.populated) 4663 return m; 4664 } while(lowLevelReceive()); 4665 4666 return waitGotNothing(); 4667 } 4668 4669 /++ 4670 Tells if [waitForNextMessage] would block. 4671 +/ 4672 /// Group: blocking_api 4673 public bool waitForNextMessageWouldBlock() { 4674 checkAgain: 4675 if(isMessageBuffered()) 4676 return false; 4677 if(!isDataPending()) 4678 return true; 4679 while(isDataPending()) 4680 if(lowLevelReceive() == false) 4681 return connectionClosedInMiddleOfMessage(); 4682 goto checkAgain; 4683 } 4684 4685 /++ 4686 Is there a message in the buffer already? 4687 If `true`, [waitForNextMessage] is guaranteed to return immediately. 4688 If `false`, check [isDataPending] as the next step. 4689 +/ 4690 /// Group: blocking_api 4691 public bool isMessageBuffered() { 4692 ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength]; 4693 auto s = d; 4694 if(d.length) { 4695 auto orig = d; 4696 auto m = WebSocketFrame.read(d); 4697 // that's how it indicates that it needs more data 4698 if(d !is orig) 4699 return true; 4700 } 4701 4702 return false; 4703 } 4704 4705 protected ubyte continuingType; 4706 protected ubyte[] continuingData; 4707 //protected size_t continuingDataLength; 4708 4709 protected WebSocketFrame processOnce() { 4710 ubyte[] d = receiveBuffer[0 .. receiveBufferUsedLength]; 4711 auto s = d; 4712 // FIXME: handle continuation frames more efficiently. it should really just reuse the receive buffer. 4713 WebSocketFrame m; 4714 if(d.length) { 4715 auto orig = d; 4716 m = WebSocketFrame.read(d); 4717 // that's how it indicates that it needs more data 4718 if(d is orig) 4719 return WebSocketFrame.init; 4720 m.unmaskInPlace(); 4721 switch(m.opcode) { 4722 case WebSocketOpcode.continuation: 4723 if(continuingData.length + m.data.length > config.maximumMessageSize) 4724 throw new Exception("message size exceeded"); 4725 4726 continuingData ~= m.data; 4727 if(m.fin) { 4728 if(ontextmessage) 4729 ontextmessage(cast(char[]) continuingData); 4730 if(onbinarymessage) 4731 onbinarymessage(continuingData); 4732 4733 continuingData = null; 4734 } 4735 break; 4736 case WebSocketOpcode.text: 4737 if(m.fin) { 4738 if(ontextmessage) 4739 ontextmessage(m.textData); 4740 } else { 4741 continuingType = m.opcode; 4742 //continuingDataLength = 0; 4743 continuingData = null; 4744 continuingData ~= m.data; 4745 } 4746 break; 4747 case WebSocketOpcode.binary: 4748 if(m.fin) { 4749 if(onbinarymessage) 4750 onbinarymessage(m.data); 4751 } else { 4752 continuingType = m.opcode; 4753 //continuingDataLength = 0; 4754 continuingData = null; 4755 continuingData ~= m.data; 4756 } 4757 break; 4758 case WebSocketOpcode.close: 4759 4760 //import std.stdio; writeln("closed ", cast(string) m.data); 4761 4762 ushort code = CloseEvent.StandardCloseCodes.noStatusCodePresent; 4763 const(char)[] reason; 4764 4765 if(m.data.length >= 2) { 4766 code = (m.data[0] << 8) | m.data[1]; 4767 reason = (cast(char[]) m.data[2 .. $]); 4768 } 4769 4770 if(onclose) 4771 onclose(CloseEvent(code, reason, true)); 4772 4773 // if we receive one and haven't sent one back we're supposed to echo it back and close. 4774 if(!closeCalled) 4775 close(code, reason.idup); 4776 4777 readyState_ = CLOSED; 4778 4779 unregisterAsActiveSocket(); 4780 llclose(); 4781 break; 4782 case WebSocketOpcode.ping: 4783 // import std.stdio; writeln("ping received ", m.data); 4784 pong(m.data); 4785 break; 4786 case WebSocketOpcode.pong: 4787 // import std.stdio; writeln("pong received ", m.data); 4788 // just really references it is still alive, nbd. 4789 break; 4790 default: // ignore though i could and perhaps should throw too 4791 } 4792 } 4793 4794 if(d.length) { 4795 m.data = m.data.dup(); 4796 } 4797 4798 import core.stdc.string; 4799 memmove(receiveBuffer.ptr, d.ptr, d.length); 4800 receiveBufferUsedLength = d.length; 4801 4802 return m; 4803 } 4804 4805 /++ 4806 Arguments for the close event. The `code` and `reason` are provided from the close message on the websocket, if they are present. The spec says code 1000 indicates a normal, default reason close, but reserves the code range from 3000-5000 for future definition; the 3000s can be registered with IANA and the 4000's are application private use. The `reason` should be user readable, but not displayed to the end user. `wasClean` is true if the server actually sent a close event, false if it just disconnected. 4807 4808 $(PITFALL 4809 The `reason` argument references a temporary buffer and there's no guarantee it will remain valid once your callback returns. It may be freed and will very likely be overwritten. If you want to keep the reason beyond the callback, make sure you `.idup` it. 4810 ) 4811 4812 History: 4813 Added March 19, 2023 (dub v11.0). 4814 +/ 4815 static struct CloseEvent { 4816 ushort code; 4817 const(char)[] reason; 4818 bool wasClean; 4819 4820 string extendedErrorInformationUnstable; 4821 4822 /++ 4823 See https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1 for details. 4824 +/ 4825 enum StandardCloseCodes { 4826 purposeFulfilled = 1000, 4827 goingAway = 1001, 4828 protocolError = 1002, 4829 unacceptableData = 1003, // e.g. got text message when you can only handle binary 4830 Reserved = 1004, 4831 noStatusCodePresent = 1005, // not set by endpoint. 4832 abnormalClosure = 1006, // not set by endpoint. closed without a Close control. FIXME: maybe keep a copy of errno around for these 4833 inconsistentData = 1007, // e.g. utf8 validation failed 4834 genericPolicyViolation = 1008, 4835 messageTooBig = 1009, 4836 clientRequiredExtensionMissing = 1010, // only the client should send this 4837 unnexpectedCondition = 1011, 4838 unverifiedCertificate = 1015, // not set by client 4839 } 4840 4841 string toString() { 4842 return cast(string) (arsd.core.toStringInternal(code) ~ ": " ~ reason); 4843 } 4844 } 4845 4846 /++ 4847 The `CloseEvent` you get references a temporary buffer that may be overwritten after your handler returns. If you want to keep it or the `event.reason` member, remember to `.idup` it. 4848 4849 History: 4850 The `CloseEvent` was changed to a [arsd.core.FlexibleDelegate] on March 19, 2023 (dub v11.0). Before that, `onclose` was a public member of type `void delegate()`. This change means setters still work with or without the [CloseEvent] argument. 4851 4852 Your onclose method is now also called on abnormal terminations. Check the `wasClean` member of the `CloseEvent` to know if it came from a close frame or other cause. 4853 +/ 4854 arsd.core.FlexibleDelegate!(void delegate(CloseEvent event)) onclose; 4855 void delegate() onerror; /// 4856 void delegate(in char[]) ontextmessage; /// 4857 void delegate(in ubyte[]) onbinarymessage; /// 4858 void delegate() onopen; /// 4859 4860 /++ 4861 4862 +/ 4863 /// Group: browser_api 4864 void onmessage(void delegate(in char[]) dg) { 4865 ontextmessage = dg; 4866 } 4867 4868 /// ditto 4869 void onmessage(void delegate(in ubyte[]) dg) { 4870 onbinarymessage = dg; 4871 } 4872 4873 /* } end copy/paste */ 4874 4875 4876 // used to decide if we mask outgoing msgs 4877 protected bool isClient; 4878 protected abstract void llsend(ubyte[] d); 4879 protected ubyte getRandomByte() @trusted { 4880 // FIXME: it is just for masking but still should be less awful 4881 __gshared ubyte seed = 0xe2; 4882 return ++seed; 4883 } 4884 protected abstract void llclose(); 4885 protected abstract void llshutdown(); 4886 public abstract bool lowLevelReceive(); 4887 protected abstract bool isDataPending(Duration timeout = 0.seconds); 4888 protected abstract void unregisterAsActiveSocket(); 4889 protected abstract WebSocketFrame waitGotNothing(); 4890 protected abstract bool connectionClosedInMiddleOfMessage(); 4891 } 4892 /* copy/paste from cgi.d */ 4893 public { 4894 enum WebSocketOpcode : ubyte { 4895 continuation = 0, 4896 text = 1, 4897 binary = 2, 4898 // 3, 4, 5, 6, 7 RESERVED 4899 close = 8, 4900 ping = 9, 4901 pong = 10, 4902 // 11,12,13,14,15 RESERVED 4903 } 4904 4905 public struct WebSocketFrame { 4906 package(arsd) bool populated; 4907 bool fin; 4908 bool rsv1; 4909 bool rsv2; 4910 bool rsv3; 4911 WebSocketOpcode opcode; // 4 bits 4912 bool masked; 4913 ubyte lengthIndicator; // don't set this when building one to send 4914 ulong realLength; // don't use when sending 4915 ubyte[4] maskingKey; // don't set this when sending 4916 ubyte[] data; 4917 4918 static WebSocketFrame simpleMessage(WebSocketOpcode opcode, in void[] data) { 4919 WebSocketFrame msg; 4920 msg.fin = true; 4921 msg.opcode = opcode; 4922 msg.data = cast(ubyte[]) data.dup; // it is mutated below when masked, so need to be cautious and copy it, sigh 4923 4924 return msg; 4925 } 4926 4927 private void send(scope void delegate(ubyte[]) llsend, scope ubyte delegate() getRandomByte) { 4928 ubyte[64] headerScratch; 4929 int headerScratchPos = 0; 4930 4931 realLength = data.length; 4932 4933 { 4934 ubyte b1; 4935 b1 |= cast(ubyte) opcode; 4936 b1 |= rsv3 ? (1 << 4) : 0; 4937 b1 |= rsv2 ? (1 << 5) : 0; 4938 b1 |= rsv1 ? (1 << 6) : 0; 4939 b1 |= fin ? (1 << 7) : 0; 4940 4941 headerScratch[0] = b1; 4942 headerScratchPos++; 4943 } 4944 4945 { 4946 headerScratchPos++; // we'll set header[1] at the end of this 4947 auto rlc = realLength; 4948 ubyte b2; 4949 b2 |= masked ? (1 << 7) : 0; 4950 4951 assert(headerScratchPos == 2); 4952 4953 if(realLength > 65535) { 4954 // use 64 bit length 4955 b2 |= 0x7f; 4956 4957 // FIXME: double check endinaness 4958 foreach(i; 0 .. 8) { 4959 headerScratch[2 + 7 - i] = rlc & 0x0ff; 4960 rlc >>>= 8; 4961 } 4962 4963 headerScratchPos += 8; 4964 } else if(realLength > 125) { 4965 // use 16 bit length 4966 b2 |= 0x7e; 4967 4968 // FIXME: double check endinaness 4969 foreach(i; 0 .. 2) { 4970 headerScratch[2 + 1 - i] = rlc & 0x0ff; 4971 rlc >>>= 8; 4972 } 4973 4974 headerScratchPos += 2; 4975 } else { 4976 // use 7 bit length 4977 b2 |= realLength & 0b_0111_1111; 4978 } 4979 4980 headerScratch[1] = b2; 4981 } 4982 4983 //assert(!masked, "masking key not properly implemented"); 4984 if(masked) { 4985 foreach(ref item; maskingKey) 4986 item = getRandomByte(); 4987 headerScratch[headerScratchPos .. headerScratchPos + 4] = maskingKey[]; 4988 headerScratchPos += 4; 4989 4990 // we'll just mask it in place... 4991 int keyIdx = 0; 4992 foreach(i; 0 .. data.length) { 4993 data[i] = data[i] ^ maskingKey[keyIdx]; 4994 if(keyIdx == 3) 4995 keyIdx = 0; 4996 else 4997 keyIdx++; 4998 } 4999 } 5000 5001 //writeln("SENDING ", headerScratch[0 .. headerScratchPos], data); 5002 llsend(headerScratch[0 .. headerScratchPos]); 5003 if(data.length) 5004 llsend(data); 5005 } 5006 5007 static WebSocketFrame read(ref ubyte[] d) { 5008 WebSocketFrame msg; 5009 5010 auto orig = d; 5011 5012 WebSocketFrame needsMoreData() { 5013 d = orig; 5014 return WebSocketFrame.init; 5015 } 5016 5017 if(d.length < 2) 5018 return needsMoreData(); 5019 5020 ubyte b = d[0]; 5021 5022 msg.populated = true; 5023 5024 msg.opcode = cast(WebSocketOpcode) (b & 0x0f); 5025 b >>= 4; 5026 msg.rsv3 = b & 0x01; 5027 b >>= 1; 5028 msg.rsv2 = b & 0x01; 5029 b >>= 1; 5030 msg.rsv1 = b & 0x01; 5031 b >>= 1; 5032 msg.fin = b & 0x01; 5033 5034 b = d[1]; 5035 msg.masked = (b & 0b1000_0000) ? true : false; 5036 msg.lengthIndicator = b & 0b0111_1111; 5037 5038 d = d[2 .. $]; 5039 5040 if(msg.lengthIndicator == 0x7e) { 5041 // 16 bit length 5042 msg.realLength = 0; 5043 5044 if(d.length < 2) return needsMoreData(); 5045 5046 foreach(i; 0 .. 2) { 5047 msg.realLength |= d[0] << ((1-i) * 8); 5048 d = d[1 .. $]; 5049 } 5050 } else if(msg.lengthIndicator == 0x7f) { 5051 // 64 bit length 5052 msg.realLength = 0; 5053 5054 if(d.length < 8) return needsMoreData(); 5055 5056 foreach(i; 0 .. 8) { 5057 msg.realLength |= ulong(d[0]) << ((7-i) * 8); 5058 d = d[1 .. $]; 5059 } 5060 } else { 5061 // 7 bit length 5062 msg.realLength = msg.lengthIndicator; 5063 } 5064 5065 if(msg.masked) { 5066 5067 if(d.length < 4) return needsMoreData(); 5068 5069 msg.maskingKey = d[0 .. 4]; 5070 d = d[4 .. $]; 5071 } 5072 5073 if(msg.realLength > d.length) { 5074 return needsMoreData(); 5075 } 5076 5077 msg.data = d[0 .. cast(size_t) msg.realLength]; 5078 d = d[cast(size_t) msg.realLength .. $]; 5079 5080 return msg; 5081 } 5082 5083 void unmaskInPlace() { 5084 if(this.masked) { 5085 int keyIdx = 0; 5086 foreach(i; 0 .. this.data.length) { 5087 this.data[i] = this.data[i] ^ this.maskingKey[keyIdx]; 5088 if(keyIdx == 3) 5089 keyIdx = 0; 5090 else 5091 keyIdx++; 5092 } 5093 } 5094 } 5095 5096 char[] textData() { 5097 return cast(char[]) data; 5098 } 5099 } 5100 } 5101 5102 /+ 5103 struct FilePathGeneric { 5104 5105 } 5106 5107 struct FilePathWin32 { 5108 5109 } 5110 5111 struct FilePathPosix { 5112 5113 } 5114 5115 struct FilePathWindowsUnc { 5116 5117 } 5118 5119 version(Windows) 5120 alias FilePath = FilePathWin32; 5121 else 5122 alias FilePath = FilePathPosix; 5123 +/ 5124 5125 5126 /++ 5127 Represents a generic async, waitable request. 5128 +/ 5129 class AsyncOperationRequest { 5130 /++ 5131 Actually issues the request, starting the operation. 5132 +/ 5133 abstract void start(); 5134 /++ 5135 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). 5136 5137 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. 5138 5139 Once a cancellation request has been sent, it cannot be undone. 5140 +/ 5141 abstract void cancel(); 5142 5143 /++ 5144 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. 5145 +/ 5146 abstract bool isComplete(); 5147 /++ 5148 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. 5149 5150 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. 5151 5152 5153 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. 5154 +/ 5155 abstract AsyncOperationResponse waitForCompletion(); 5156 5157 /++ 5158 5159 +/ 5160 // abstract void repeat(); 5161 } 5162 5163 /++ 5164 5165 +/ 5166 interface AsyncOperationResponse { 5167 /++ 5168 Returns true if the request completed successfully, finishing what it was supposed to. 5169 5170 Should be set to `false` if the request was cancelled before completing or encountered an error. 5171 +/ 5172 bool wasSuccessful(); 5173 } 5174 5175 /++ 5176 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. 5177 5178 Please note that "completion" is not necessary successful completion; a request being cancelled or encountering an error also counts as it being completed. 5179 5180 The `waitForFirstToCompleteByIndex` version instead returns the index of the array entry that completed first. 5181 5182 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. 5183 5184 You might prefer using [asTheyComplete], which will give each request as it completes and loop over until all of them are complete. 5185 5186 Returns: 5187 `null` or `requests.length` if none completed before returning. 5188 +/ 5189 AsyncOperationRequest waitForFirstToComplete(AsyncOperationRequest[] requests...) { 5190 auto idx = waitForFirstToCompleteByIndex(requests); 5191 if(idx == requests.length) 5192 return null; 5193 return requests[idx]; 5194 } 5195 /// ditto 5196 size_t waitForFirstToCompleteByIndex(AsyncOperationRequest[] requests...) { 5197 size_t helper() { 5198 foreach(idx, request; requests) 5199 if(request.isComplete()) 5200 return idx; 5201 return requests.length; 5202 } 5203 5204 auto idx = helper(); 5205 // if one is already done, return it 5206 if(idx != requests.length) 5207 return idx; 5208 5209 // otherwise, run the ad-hoc event loop until one is 5210 // FIXME: what if we are inside a fiber? 5211 auto el = getThisThreadEventLoop(); 5212 el.run(() => (idx = helper()) != requests.length); 5213 5214 return idx; 5215 } 5216 5217 /++ 5218 Waits for all the `requests` to complete, giving each one through the range interface as it completes. 5219 5220 This meant to be used in a foreach loop. 5221 5222 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). 5223 +/ 5224 AsTheyCompleteRange asTheyComplete(AsyncOperationRequest[] requests...) { 5225 return AsTheyCompleteRange(requests); 5226 } 5227 /// ditto 5228 struct AsTheyCompleteRange { 5229 AsyncOperationRequest[] requests; 5230 5231 this(AsyncOperationRequest[] requests) { 5232 this.requests = requests; 5233 5234 if(requests.length == 0) 5235 return; 5236 5237 // wait for first one to complete, then move it to the front of the array 5238 moveFirstCompleteToFront(); 5239 } 5240 5241 private void moveFirstCompleteToFront() { 5242 auto idx = waitForFirstToCompleteByIndex(requests); 5243 5244 auto tmp = requests[0]; 5245 requests[0] = requests[idx]; 5246 requests[idx] = tmp; 5247 } 5248 5249 bool empty() { 5250 return requests.length == 0; 5251 } 5252 5253 void popFront() { 5254 assert(!empty); 5255 /+ 5256 this needs to 5257 1) remove the front of the array as being already processed (unless it is the initial priming call) 5258 2) wait for one of them to complete 5259 3) move the complete one to the front of the array 5260 +/ 5261 5262 requests[0] = requests[$-1]; 5263 requests = requests[0 .. $-1]; 5264 5265 if(requests.length) 5266 moveFirstCompleteToFront(); 5267 } 5268 5269 AsyncOperationRequest front() { 5270 return requests[0]; 5271 } 5272 } 5273 5274 version(Windows) { 5275 alias NativeFileHandle = HANDLE; /// 5276 alias NativeSocketHandle = SOCKET; /// 5277 alias NativePipeHandle = HANDLE; /// 5278 } else version(Posix) { 5279 alias NativeFileHandle = int; /// 5280 alias NativeSocketHandle = int; /// 5281 alias NativePipeHandle = int; /// 5282 } 5283 5284 /++ 5285 An `AbstractFile` represents a file handle on the operating system level. You cannot do much with it. 5286 +/ 5287 version(HasFile) class AbstractFile { 5288 private { 5289 NativeFileHandle handle; 5290 } 5291 5292 /++ 5293 +/ 5294 enum OpenMode { 5295 readOnly, /// C's "r", the file is read 5296 writeWithTruncation, /// C's "w", the file is blanked upon opening so it only holds what you write 5297 appendOnly, /// C's "a", writes will always be appended to the file 5298 readAndWrite /// C's "r+", writes will overwrite existing parts of the file based on where you seek (default is at the beginning) 5299 } 5300 5301 /++ 5302 +/ 5303 enum RequirePreexisting { 5304 no, 5305 yes 5306 } 5307 5308 /+ 5309 enum SpecialFlags { 5310 randomAccessExpected, /// FILE_FLAG_SEQUENTIAL_SCAN is turned off and posix_fadvise(POSIX_FADV_SEQUENTIAL) 5311 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 5312 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. 5313 deleteWhenClosed, /// Windows has a flag for this but idk if it is of any real use 5314 async, /// open it in overlapped mode, all reads and writes must then provide an offset. Only implemented on Windows 5315 } 5316 +/ 5317 5318 /++ 5319 5320 +/ 5321 protected this(bool async, FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0) { 5322 version(Windows) { 5323 DWORD access; 5324 DWORD creation; 5325 5326 final switch(mode) { 5327 case OpenMode.readOnly: 5328 access = GENERIC_READ; 5329 creation = OPEN_EXISTING; 5330 break; 5331 case OpenMode.writeWithTruncation: 5332 access = GENERIC_WRITE; 5333 5334 final switch(require) { 5335 case RequirePreexisting.no: 5336 creation = CREATE_ALWAYS; 5337 break; 5338 case RequirePreexisting.yes: 5339 creation = TRUNCATE_EXISTING; 5340 break; 5341 } 5342 break; 5343 case OpenMode.appendOnly: 5344 access = FILE_APPEND_DATA; 5345 5346 final switch(require) { 5347 case RequirePreexisting.no: 5348 creation = OPEN_ALWAYS; 5349 break; 5350 case RequirePreexisting.yes: 5351 creation = OPEN_EXISTING; 5352 break; 5353 } 5354 break; 5355 case OpenMode.readAndWrite: 5356 access = GENERIC_READ | GENERIC_WRITE; 5357 5358 final switch(require) { 5359 case RequirePreexisting.no: 5360 creation = CREATE_NEW; 5361 break; 5362 case RequirePreexisting.yes: 5363 creation = OPEN_EXISTING; 5364 break; 5365 } 5366 break; 5367 } 5368 5369 WCharzBuffer wname = WCharzBuffer(filename.path); 5370 5371 auto handle = CreateFileW( 5372 wname.ptr, 5373 access, 5374 FILE_SHARE_READ, 5375 null, 5376 creation, 5377 FILE_ATTRIBUTE_NORMAL | (async ? FILE_FLAG_OVERLAPPED : 0), 5378 null 5379 ); 5380 5381 if(handle == INVALID_HANDLE_VALUE) { 5382 // FIXME: throw the filename and other params here too 5383 SavedArgument[3] args; 5384 args[0] = SavedArgument("filename", LimitedVariant(filename.path)); 5385 args[1] = SavedArgument("access", LimitedVariant(access, 2)); 5386 args[2] = SavedArgument("requirePreexisting", LimitedVariant(require == RequirePreexisting.yes)); 5387 throw new WindowsApiException("CreateFileW", GetLastError(), args[]); 5388 } 5389 5390 this.handle = handle; 5391 } else version(Posix) { 5392 import core.sys.posix.unistd; 5393 import core.sys.posix.fcntl; 5394 5395 CharzBuffer namez = CharzBuffer(filename.path); 5396 int flags; 5397 5398 // FIXME does mac not have cloexec for real or is this just a druntime problem????? 5399 version(Arsd_core_has_cloexec) { 5400 flags = O_CLOEXEC; 5401 } else { 5402 scope(success) 5403 setCloExec(this.handle); 5404 } 5405 5406 if(async) 5407 flags |= O_NONBLOCK; 5408 5409 final switch(mode) { 5410 case OpenMode.readOnly: 5411 flags |= O_RDONLY; 5412 break; 5413 case OpenMode.writeWithTruncation: 5414 flags |= O_WRONLY | O_TRUNC; 5415 5416 final switch(require) { 5417 case RequirePreexisting.no: 5418 flags |= O_CREAT; 5419 break; 5420 case RequirePreexisting.yes: 5421 break; 5422 } 5423 break; 5424 case OpenMode.appendOnly: 5425 flags |= O_WRONLY | O_APPEND; 5426 5427 final switch(require) { 5428 case RequirePreexisting.no: 5429 flags |= O_CREAT; 5430 break; 5431 case RequirePreexisting.yes: 5432 break; 5433 } 5434 break; 5435 case OpenMode.readAndWrite: 5436 flags |= O_RDWR; 5437 5438 final switch(require) { 5439 case RequirePreexisting.no: 5440 flags |= O_CREAT; 5441 break; 5442 case RequirePreexisting.yes: 5443 break; 5444 } 5445 break; 5446 } 5447 5448 auto perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; 5449 int fd = open(namez.ptr, flags, perms); 5450 if(fd == -1) { 5451 SavedArgument[3] args; 5452 args[0] = SavedArgument("filename", LimitedVariant(filename.path)); 5453 args[1] = SavedArgument("flags", LimitedVariant(flags, 2)); 5454 args[2] = SavedArgument("perms", LimitedVariant(perms, 8)); 5455 throw new ErrnoApiException("open", errno, args[]); 5456 } 5457 5458 this.handle = fd; 5459 } 5460 } 5461 5462 /++ 5463 5464 +/ 5465 private this(NativeFileHandle handleToWrap) { 5466 this.handle = handleToWrap; 5467 } 5468 5469 // only available on some types of file 5470 long size() { return 0; } 5471 5472 // note that there is no fsync thing, instead use the special flag. 5473 5474 /++ 5475 5476 +/ 5477 void close() { 5478 version(Windows) { 5479 Win32Enforce!CloseHandle(handle); 5480 handle = null; 5481 } else version(Posix) { 5482 import unix = core.sys.posix.unistd; 5483 import core.sys.posix.fcntl; 5484 5485 ErrnoEnforce!(unix.close)(handle); 5486 handle = -1; 5487 } 5488 } 5489 5490 NativeFileHandle nativeHandle() { 5491 return this.handle; 5492 } 5493 } 5494 5495 /++ 5496 5497 +/ 5498 version(HasFile) class File : AbstractFile { 5499 5500 /++ 5501 Opens a file in synchronous access mode. 5502 5503 The permission mask is on used on posix systems FIXME: implement it 5504 +/ 5505 this(FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0, uint permMask = 0) { 5506 super(false, filename, mode, require, specialFlags); 5507 } 5508 5509 this(NativeFileHandle wrapWithoutOtherwiseChanging) { 5510 super(wrapWithoutOtherwiseChanging); 5511 } 5512 5513 /++ 5514 5515 +/ 5516 ubyte[] read(scope ubyte[] buffer) { 5517 return null; 5518 } 5519 5520 /++ 5521 5522 +/ 5523 void write(in void[] buffer) { 5524 } 5525 5526 enum Seek { 5527 current, 5528 fromBeginning, 5529 fromEnd 5530 } 5531 5532 // Seeking/telling/sizing is not permitted when appending and some files don't support it 5533 // also not permitted in async mode 5534 void seek(long where, Seek fromWhence) {} 5535 long tell() { return 0; } 5536 } 5537 5538 /++ 5539 Only one operation can be pending at any time in the current implementation. 5540 +/ 5541 version(HasFile) class AsyncFile : AbstractFile { 5542 /++ 5543 Opens a file in asynchronous access mode. 5544 +/ 5545 this(FilePath filename, OpenMode mode = OpenMode.readOnly, RequirePreexisting require = RequirePreexisting.no, uint specialFlags = 0, uint permissionMask = 0) { 5546 // FIXME: implement permissionMask 5547 super(true, filename, mode, require, specialFlags); 5548 } 5549 5550 package(arsd) this(NativeFileHandle adoptPreSetup) { 5551 super(adoptPreSetup); 5552 } 5553 5554 /// 5555 AsyncReadRequest read(ubyte[] buffer, long offset = 0) { 5556 return new AsyncReadRequest(this, buffer, offset); 5557 } 5558 5559 /// 5560 AsyncWriteRequest write(const(void)[] buffer, long offset = 0) { 5561 return new AsyncWriteRequest(this, cast(ubyte[]) buffer, offset); 5562 } 5563 5564 } 5565 else class AsyncFile { 5566 package(arsd) this(NativeFileHandle adoptPreSetup) {} 5567 } 5568 5569 /++ 5570 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. 5571 5572 Tip: prefer the callback ones. If settings where async is possible, it will do async, and if not, it will sync. 5573 5574 NOT FULLY IMPLEMENTED 5575 +/ 5576 void writeFile(string filename, const(void)[] contents) { 5577 // FIXME: stop using the C lib and start error checking 5578 import core.stdc.stdio; 5579 CharzBuffer fn = filename; 5580 auto file = fopen(fn.ptr, "wb"); 5581 if(file is null) 5582 throw new ErrnoApiException("fopen", errno, [SavedArgument("filename", LimitedVariant(filename))]); 5583 fwrite(contents.ptr, 1, contents.length, file); 5584 fclose(file); 5585 } 5586 5587 /// ditto 5588 const(ubyte[]) readBinaryFile(string filename) { 5589 // FIXME: stop using the C lib and check for more errors 5590 5591 import core.stdc.stdio; 5592 CharzBuffer fn = filename; 5593 auto file = fopen(fn.ptr, "rb"); 5594 if(file is null) 5595 throw new ErrnoApiException("fopen", errno, [SavedArgument("filename", LimitedVariant(filename))]); 5596 ubyte[] buffer = new ubyte[](64 * 1024); 5597 ubyte[] contents; 5598 5599 while(true) { 5600 auto ret = fread(buffer.ptr, 1, buffer.length, file); 5601 if(ret < buffer.length) { 5602 if(contents is null) 5603 contents = buffer[0 .. ret]; 5604 else 5605 contents ~= buffer[0 .. ret]; 5606 break; 5607 } else { 5608 contents ~= buffer[0 .. ret]; 5609 } 5610 } 5611 fclose(file); 5612 5613 return contents; 5614 } 5615 5616 /// ditto 5617 string readTextFile(string filename, string fileEncoding = null) { 5618 return cast(string) readBinaryFile(filename); 5619 } 5620 5621 /+ 5622 private Class recycleObject(Class, Args...)(Class objectToRecycle, Args args) { 5623 if(objectToRecycle is null) 5624 return new Class(args); 5625 // destroy nulls out the vtable which is the first thing in the object 5626 // so if it hasn't already been destroyed, we'll do it here 5627 if((*cast(void**) objectToRecycle) !is null) { 5628 assert(typeid(objectToRecycle) is typeid(Class)); // to make sure we're actually recycling the right kind of object 5629 .destroy(objectToRecycle); 5630 } 5631 5632 // then go ahead and reinitialize it 5633 ubyte[] rawData = (cast(ubyte*) cast(void*) objectToRecycle)[0 .. __traits(classInstanceSize, Class)]; 5634 rawData[] = (cast(ubyte[]) typeid(Class).initializer)[]; 5635 5636 objectToRecycle.__ctor(args); 5637 5638 return objectToRecycle; 5639 } 5640 +/ 5641 5642 /+ 5643 /++ 5644 Preallocates a class object without initializing it. 5645 5646 This is suitable *only* for passing to one of the functions in here that takes a preallocated object for recycling. 5647 +/ 5648 Class preallocate(Class)() { 5649 import core.memory; 5650 // FIXME: can i pass NO_SCAN here? 5651 return cast(Class) GC.calloc(__traits(classInstanceSize, Class), 0, typeid(Class)); 5652 } 5653 5654 OwnedClass!Class preallocateOnStack(Class)() { 5655 5656 } 5657 +/ 5658 5659 // thanks for a random person on stack overflow for this function 5660 version(Windows) 5661 BOOL MyCreatePipeEx( 5662 PHANDLE lpReadPipe, 5663 PHANDLE lpWritePipe, 5664 LPSECURITY_ATTRIBUTES lpPipeAttributes, 5665 DWORD nSize, 5666 DWORD dwReadMode, 5667 DWORD dwWriteMode 5668 ) 5669 { 5670 HANDLE ReadPipeHandle, WritePipeHandle; 5671 DWORD dwError; 5672 CHAR[MAX_PATH] PipeNameBuffer; 5673 5674 if (nSize == 0) { 5675 nSize = 4096; 5676 } 5677 5678 // FIXME: should be atomic op and gshared 5679 static shared(int) PipeSerialNumber = 0; 5680 5681 import core.stdc.string; 5682 import core.stdc.stdio; 5683 5684 sprintf(PipeNameBuffer.ptr, 5685 "\\\\.\\Pipe\\ArsdCoreAnonymousPipe.%08x.%08x".ptr, 5686 GetCurrentProcessId(), 5687 atomicOp!"+="(PipeSerialNumber, 1) 5688 ); 5689 5690 ReadPipeHandle = CreateNamedPipeA( 5691 PipeNameBuffer.ptr, 5692 1/*PIPE_ACCESS_INBOUND*/ | dwReadMode, 5693 0/*PIPE_TYPE_BYTE*/ | 0/*PIPE_WAIT*/, 5694 1, // Number of pipes 5695 nSize, // Out buffer size 5696 nSize, // In buffer size 5697 120 * 1000, // Timeout in ms 5698 lpPipeAttributes 5699 ); 5700 5701 if (! ReadPipeHandle) { 5702 return FALSE; 5703 } 5704 5705 WritePipeHandle = CreateFileA( 5706 PipeNameBuffer.ptr, 5707 GENERIC_WRITE, 5708 0, // No sharing 5709 lpPipeAttributes, 5710 OPEN_EXISTING, 5711 FILE_ATTRIBUTE_NORMAL | dwWriteMode, 5712 null // Template file 5713 ); 5714 5715 if (INVALID_HANDLE_VALUE == WritePipeHandle) { 5716 dwError = GetLastError(); 5717 CloseHandle( ReadPipeHandle ); 5718 SetLastError(dwError); 5719 return FALSE; 5720 } 5721 5722 *lpReadPipe = ReadPipeHandle; 5723 *lpWritePipe = WritePipeHandle; 5724 return( TRUE ); 5725 } 5726 5727 5728 5729 /+ 5730 5731 // this is probably useless. 5732 5733 /++ 5734 Creates a pair of anonymous pipes ready for async operations. 5735 5736 You can pass some preallocated objects to recycle if you like. 5737 +/ 5738 AsyncAnonymousPipe[2] anonymousPipePair(AsyncAnonymousPipe[2] preallocatedObjects = [null, null], bool inheritable = false) { 5739 version(Posix) { 5740 int[2] fds; 5741 auto ret = pipe(fds); 5742 5743 if(ret == -1) 5744 throw new SystemApiException("pipe", errno); 5745 5746 // FIXME: do we want them inheritable? and do we want both sides to be async? 5747 if(!inheritable) { 5748 setCloExec(fds[0]); 5749 setCloExec(fds[1]); 5750 } 5751 // if it is inherited, do we actually want it non-blocking? 5752 makeNonBlocking(fds[0]); 5753 makeNonBlocking(fds[1]); 5754 5755 return [ 5756 recycleObject(preallocatedObjects[0], fds[0]), 5757 recycleObject(preallocatedObjects[1], fds[1]), 5758 ]; 5759 } else version(Windows) { 5760 HANDLE rp, wp; 5761 // FIXME: do we want them inheritable? and do we want both sides to be async? 5762 if(!MyCreatePipeEx(&rp, &wp, null, 0, FILE_FLAG_OVERLAPPED, FILE_FLAG_OVERLAPPED)) 5763 throw new SystemApiException("MyCreatePipeEx", GetLastError()); 5764 return [ 5765 recycleObject(preallocatedObjects[0], rp), 5766 recycleObject(preallocatedObjects[1], wp), 5767 ]; 5768 } else throw ArsdException!"NotYetImplemented"(); 5769 } 5770 // on posix, just do pipe() w/ non block 5771 // on windows, do an overlapped named pipe server, connect, stop listening, return pair. 5772 +/ 5773 5774 /+ 5775 class NamedPipe : AsyncFile { 5776 5777 } 5778 +/ 5779 5780 /++ 5781 A named pipe ready to accept connections. 5782 5783 A Windows named pipe is an IPC mechanism usable on local machines or across a Windows network. 5784 +/ 5785 version(Windows) 5786 class NamedPipeServer { 5787 // unix domain socket or windows named pipe 5788 5789 // Promise!AsyncAnonymousPipe connect; 5790 // Promise!AsyncAnonymousPipe accept; 5791 5792 // when a new connection arrives, it calls your callback 5793 // can be on a specific thread or on any thread 5794 } 5795 5796 private version(Windows) extern(Windows) { 5797 const(char)* inet_ntop(int, const void*, char*, socklen_t); 5798 } 5799 5800 /++ 5801 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. 5802 5803 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. 5804 5805 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. 5806 5807 $(TIP 5808 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! 5809 ) 5810 5811 History: 5812 Added August 4, 2023 (dub v11.0) 5813 +/ 5814 struct UserProvidedBuffer(T) { 5815 private T[] buffer; 5816 private int actualLength; 5817 private OnOutOfSpace policy; 5818 5819 /++ 5820 5821 +/ 5822 public this(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate) { 5823 this.buffer = buffer; 5824 this.policy = policy; 5825 } 5826 5827 package(arsd) bool append(T item) { 5828 if(actualLength < buffer.length) { 5829 buffer[actualLength++] = item; 5830 return true; 5831 } else final switch(policy) { 5832 case OnOutOfSpace.discard: 5833 return false; 5834 case OnOutOfSpace.exception: 5835 throw ArsdException!"Buffer out of space"(buffer.length, actualLength); 5836 case OnOutOfSpace.reallocate: 5837 buffer ~= item; 5838 actualLength++; 5839 return true; 5840 } 5841 } 5842 5843 package(arsd) T[] slice() { 5844 return buffer[0 .. actualLength]; 5845 } 5846 } 5847 5848 /// ditto 5849 UserProvidedBuffer!T provideBuffer(T)(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate) { 5850 return UserProvidedBuffer!T(buffer, policy); 5851 } 5852 5853 /++ 5854 Possible policies for [UserProvidedBuffer]s that run out of space. 5855 +/ 5856 enum OnOutOfSpace { 5857 reallocate, /// reallocate the buffer with the GC to make room 5858 discard, /// discard all contents that do not fit in your provided buffer 5859 exception, /// throw an exception if there is data that would not fit in your provided buffer 5860 } 5861 5862 5863 5864 /+ 5865 The GC can be called from any thread, and a lot of cleanup must be done 5866 on the gui thread. Since the GC can interrupt any locks - including being 5867 triggered inside a critical section - it is vital to avoid deadlocks to get 5868 these functions called from the right place. 5869 5870 If the buffer overflows, things are going to get leaked. I'm kinda ok with that 5871 right now. 5872 5873 The cleanup function is run when the event loop gets around to it, which is just 5874 whenever there's something there after it has been woken up for other work. It does 5875 NOT wake up the loop itself - can't risk doing that from inside the GC in another thread. 5876 (Well actually it might be ok but i don't wanna mess with it right now.) 5877 +/ 5878 package(arsd) struct CleanupQueue { 5879 import core.stdc.stdlib; 5880 5881 void queue(alias func, T...)(T args) { 5882 static struct Args { 5883 T args; 5884 } 5885 static struct RealJob { 5886 Job j; 5887 Args a; 5888 } 5889 static void call(Job* data) { 5890 auto rj = cast(RealJob*) data; 5891 func(rj.a.args); 5892 } 5893 5894 RealJob* thing = cast(RealJob*) malloc(RealJob.sizeof); 5895 thing.j.call = &call; 5896 thing.a.args = args; 5897 5898 buffer[tail++] = cast(Job*) thing; 5899 5900 // FIXME: set overflowed 5901 } 5902 5903 void process() { 5904 const tail = this.tail; 5905 5906 while(tail != head) { 5907 Job* job = cast(Job*) buffer[head++]; 5908 job.call(job); 5909 free(job); 5910 } 5911 5912 if(overflowed) 5913 throw new object.Exception("cleanup overflowed"); 5914 } 5915 5916 private: 5917 5918 ubyte tail; // must ONLY be written by queue 5919 ubyte head; // must ONLY be written by process 5920 bool overflowed; 5921 5922 static struct Job { 5923 void function(Job*) call; 5924 } 5925 5926 void*[256] buffer; 5927 } 5928 package(arsd) __gshared CleanupQueue cleanupQueue; 5929 5930 5931 5932 5933 /++ 5934 A timer that will trigger your function on a given interval. 5935 5936 5937 You create a timer with an interval and a callback. It will continue 5938 to fire on the interval until it is destroyed. 5939 5940 --- 5941 auto timer = new Timer(50, { it happened!; }); 5942 timer.destroy(); 5943 --- 5944 5945 Timers can only be expected to fire when the event loop is running and only 5946 once per iteration through the event loop. 5947 5948 History: 5949 Prior to December 9, 2020, a timer pulse set too high with a handler too 5950 slow could lock up the event loop. It now guarantees other things will 5951 get a chance to run between timer calls, even if that means not keeping up 5952 with the requested interval. 5953 5954 Originally part of arsd.simpledisplay, this code was integrated into 5955 arsd.core on May 26, 2024 (committed on June 10). 5956 +/ 5957 version(HasTimer) 5958 class Timer { 5959 // FIXME: absolute time vs relative time 5960 // FIXME: real time? 5961 5962 // FIXME: I might add overloads for ones that take a count of 5963 // how many elapsed since last time (on Windows, it will divide 5964 // the ticks thing given, on Linux it is just available) and 5965 // maybe one that takes an instance of the Timer itself too 5966 5967 5968 /++ 5969 Creates an initialized, but unarmed timer. You must call other methods later. 5970 +/ 5971 this(bool actuallyInitialize = true) { 5972 if(actuallyInitialize) 5973 initialize(); 5974 } 5975 5976 private void initialize() { 5977 version(Windows) { 5978 handle = CreateWaitableTimer(null, false, null); 5979 if(handle is null) 5980 throw new WindowsApiException("CreateWaitableTimer", GetLastError()); 5981 cbh = new CallbackHelper(&trigger); 5982 } else version(Emscripten) { 5983 assert(0); 5984 } else version(linux) { 5985 import core.sys.linux.timerfd; 5986 5987 fd = timerfd_create(CLOCK_MONOTONIC, 0); 5988 if(fd == -1) 5989 throw new Exception("timer create failed"); 5990 5991 auto el = getThisThreadEventLoop(EventLoopType.Ui); 5992 unregisterToken = el.addCallbackOnFdReadable(fd, new CallbackHelper(&trigger)); 5993 } else version(Arsd_core_kqueue) { 5994 this.ident = ++identTicker; 5995 } else throw new NotYetImplementedException(); 5996 // FIXME: freebsd 12 has timer_fd and netbsd 10 too 5997 } 5998 5999 /++ 6000 +/ 6001 void setPulseCallback(void delegate() onPulse) { 6002 assert(onPulse !is null); 6003 this.onPulse = onPulse; 6004 } 6005 6006 /++ 6007 +/ 6008 void changeTime(int intervalInMilliseconds, bool repeats) { 6009 this.intervalInMilliseconds = intervalInMilliseconds; 6010 this.repeats = repeats; 6011 changeTimeInternal(intervalInMilliseconds, repeats); 6012 } 6013 6014 private void changeTimeInternal(int intervalInMilliseconds, bool repeats) { 6015 version(Windows) 6016 { 6017 LARGE_INTEGER initialTime; 6018 initialTime.QuadPart = -intervalInMilliseconds * 10000000L / 1000; // Windows wants hnsecs, we have msecs 6019 if(!SetWaitableTimer(handle, &initialTime, repeats ? intervalInMilliseconds : 0, &timerCallback, cast(void*) cbh, false)) 6020 throw new WindowsApiException("SetWaitableTimer", GetLastError()); 6021 } else version(Emscripten) { 6022 assert(0); 6023 } else version(linux) { 6024 import core.sys.linux.timerfd; 6025 6026 itimerspec value = makeItimerspec(intervalInMilliseconds, repeats); 6027 if(timerfd_settime(fd, 0, &value, null) == -1) { 6028 throw new ErrnoApiException("couldn't change pulse timer", errno); 6029 } 6030 } else version(Arsd_core_kqueue) { 6031 // FIXME 6032 6033 auto el = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 6034 6035 kevent_t ev; 6036 6037 cbh = new CallbackHelper(&trigger); 6038 6039 EV_SET(&ev, this.ident, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_CLEAR | (repeats ? 0 : EV_ONESHOT), NOTE_USECONDS, 1000 * intervalInMilliseconds, cast(void*) cbh); 6040 ErrnoEnforce!kevent(el.kqueuefd, &ev, 1, null, 0, null); 6041 } else { 6042 throw new NotYetImplementedException(); 6043 } 6044 // FIXME: freebsd 12 has timer_fd and netbsd 10 too 6045 } 6046 6047 /++ 6048 +/ 6049 void pause() { 6050 // FIXME this kinda makes little sense tbh 6051 // when it restarts, it won't be on the same rhythm as it was at first... 6052 changeTimeInternal(0, false); 6053 } 6054 6055 /++ 6056 +/ 6057 void unpause() { 6058 changeTimeInternal(this.intervalInMilliseconds, this.repeats); 6059 } 6060 6061 /++ 6062 +/ 6063 void cancel() { 6064 version(Windows) 6065 CancelWaitableTimer(handle); 6066 else 6067 changeTime(0, false); 6068 } 6069 6070 6071 /++ 6072 Create a timer with a callback when it triggers. 6073 +/ 6074 this(int intervalInMilliseconds, void delegate() onPulse, bool repeats = true) @trusted { 6075 assert(onPulse !is null); 6076 6077 initialize(); 6078 setPulseCallback(onPulse); 6079 changeTime(intervalInMilliseconds, repeats); 6080 } 6081 6082 /++ 6083 Sets a one-of timer that happens some time after the given timestamp, then destroys itself 6084 +/ 6085 this(SimplifiedUtcTimestamp when, void delegate() onTimeArrived) { 6086 import core.stdc.time; 6087 auto ts = when.toUnixTime; 6088 auto now = time(null); 6089 if(ts <= now) { 6090 this(false); 6091 onTimeArrived(); 6092 } else { 6093 // FIXME: should use the OS facilities to set the actual time on the real time clock 6094 auto dis = this; 6095 this(cast(int)(ts - now) * 1000, () { 6096 onTimeArrived(); 6097 dis.cancel(); 6098 dis.dispose(); 6099 }, false); 6100 } 6101 } 6102 6103 version(Windows) {} else version(Arsd_core_kqueue) {} else { 6104 ICoreEventLoop.UnregisterToken unregisterToken; 6105 } 6106 6107 // just cuz I sometimes call it this. 6108 alias dispose = destroy; 6109 6110 /++ 6111 Stop and destroy the timer object. 6112 6113 You should not use it again after destroying it. 6114 +/ 6115 void destroy() { 6116 version(Windows) { 6117 cbh.release(); 6118 staticDestroy(handle); 6119 handle = null; 6120 } else version(linux) { 6121 unregisterToken.unregister(); 6122 staticDestroy(fd); 6123 fd = -1; 6124 } else version(Arsd_core_kqueue) { 6125 } else throw new NotYetImplementedException(); 6126 } 6127 6128 ~this() { 6129 version(Windows) { 6130 if(handle) 6131 cleanupQueue.queue!staticDestroy(handle); 6132 } else version(linux) { 6133 cleanupQueue.queue!unregister(unregisterToken); 6134 if(fd != -1) 6135 cleanupQueue.queue!staticDestroy(fd); 6136 } 6137 } 6138 6139 6140 private: 6141 6142 version(Windows) 6143 static void staticDestroy(HANDLE handle) { 6144 if(handle) { 6145 // KillTimer(null, handle); 6146 CancelWaitableTimer(cast(void*)handle); 6147 CloseHandle(handle); 6148 } 6149 } 6150 else version(linux) 6151 static void staticDestroy(int fd) @system { 6152 if(fd != -1) { 6153 import unix = core.sys.posix.unistd; 6154 6155 unix.close(fd); 6156 } 6157 } 6158 6159 version(Windows) {} else 6160 static void unregister(arsd.core.ICoreEventLoop.UnregisterToken urt) { 6161 if(urt.impl !is null) 6162 urt.unregister(); 6163 } 6164 6165 6166 void delegate() onPulse; 6167 int intervalInMilliseconds; 6168 bool repeats; 6169 6170 int lastEventLoopRoundTriggered; 6171 6172 version(linux) { 6173 static auto makeItimerspec(int intervalInMilliseconds, bool repeats) { 6174 import core.sys.linux.timerfd; 6175 6176 itimerspec value; 6177 value.it_value.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6178 value.it_value.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6179 6180 if(repeats) { 6181 value.it_interval.tv_sec = cast(int) (intervalInMilliseconds / 1000); 6182 value.it_interval.tv_nsec = (intervalInMilliseconds % 1000) * 1000_000; 6183 } 6184 6185 return value; 6186 } 6187 } 6188 6189 void trigger() { 6190 version(linux) { 6191 import unix = core.sys.posix.unistd; 6192 long val; 6193 unix.read(fd, &val, val.sizeof); // gotta clear the pipe 6194 } else version(Windows) { 6195 if(this.lastEventLoopRoundTriggered == eventLoopRound) 6196 return; // never try to actually run faster than the event loop 6197 lastEventLoopRoundTriggered = eventLoopRound; 6198 } else version(Arsd_core_kqueue) { 6199 } else throw new NotYetImplementedException(); 6200 6201 if(onPulse) 6202 onPulse(); 6203 } 6204 6205 version(Windows) 6206 extern(Windows) 6207 //static void timerCallback(HWND, UINT, UINT_PTR timer, DWORD dwTime) nothrow { 6208 static void timerCallback(void* timer, DWORD lowTime, DWORD hiTime) nothrow { 6209 auto cbh = cast(CallbackHelper) timer; 6210 try 6211 cbh.call(); 6212 catch(Throwable e) { sdpy_abort(e); assert(0); } 6213 } 6214 6215 version(Windows) { 6216 HANDLE handle; 6217 CallbackHelper cbh; 6218 } else version(linux) { 6219 int fd = -1; 6220 } else version(Arsd_core_kqueue) { 6221 int ident; 6222 static int identTicker; 6223 CallbackHelper cbh; 6224 } else static if(UseCocoa) { 6225 } else static assert(0, "timer not supported"); 6226 } 6227 6228 version(Windows) 6229 private void sdpy_abort(Throwable e) nothrow { 6230 try 6231 MessageBoxA(null, (e.toString() ~ "\0").ptr, "Exception caught in WndProc", 0); 6232 catch(Exception e) 6233 MessageBoxA(null, "Exception.toString threw too!", "Exception caught in WndProc", 0); 6234 ExitProcess(1); 6235 } 6236 6237 6238 private int eventLoopRound = -1; // so things that assume 0 still work eg lastEventLoopRoundTriggered 6239 6240 6241 6242 /++ 6243 For functions that give you an unknown address, you can use this to hold it. 6244 6245 Can get: 6246 ip4 6247 ip6 6248 unix 6249 abstract_ 6250 6251 name lookup for connect (stream or dgram) 6252 request canonical name? 6253 6254 interface lookup for bind (stream or dgram) 6255 +/ 6256 version(HasSocket) struct SocketAddress { 6257 import core.sys.posix.netdb; 6258 6259 /++ 6260 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). 6261 6262 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. 6263 +/ 6264 static SocketAddress[] localhost(ushort port, return UserProvidedBuffer!SocketAddress buffer = null) { 6265 buffer.append(ip6("::1", port)); 6266 buffer.append(ip4("127.0.0.1", port)); 6267 return buffer.slice; 6268 } 6269 6270 /// ditto 6271 static SocketAddress[] allInterfaces(ushort port, return UserProvidedBuffer!SocketAddress buffer = null) { 6272 char[16] str; 6273 return allInterfaces(intToString(port, str[]), buffer); 6274 } 6275 6276 /// ditto 6277 static SocketAddress[] allInterfaces(scope const char[] serviceOrPort, return UserProvidedBuffer!SocketAddress buffer = null) { 6278 addrinfo hints; 6279 hints.ai_flags = AI_PASSIVE; 6280 hints.ai_socktype = SOCK_STREAM; // just to filter it down a little tbh 6281 return get(null, serviceOrPort, &hints, buffer); 6282 } 6283 6284 /++ 6285 Returns a single address object for the given protocol and parameters. 6286 6287 You probably should generally prefer [get], [localhost], or [allInterfaces] to have more flexible code. 6288 +/ 6289 static SocketAddress ip4(scope const char[] address, ushort port, bool forListening = false) { 6290 return getSingleAddress(AF_INET, AI_NUMERICHOST | (forListening ? AI_PASSIVE : 0), address, port); 6291 } 6292 6293 /// ditto 6294 static SocketAddress ip4(ushort port) { 6295 return ip4(null, port, true); 6296 } 6297 6298 /// ditto 6299 static SocketAddress ip6(scope const char[] address, ushort port, bool forListening = false) { 6300 return getSingleAddress(AF_INET6, AI_NUMERICHOST | (forListening ? AI_PASSIVE : 0), address, port); 6301 } 6302 6303 /// ditto 6304 static SocketAddress ip6(ushort port) { 6305 return ip6(null, port, true); 6306 } 6307 6308 /// ditto 6309 static SocketAddress unix(scope const char[] path) { 6310 // FIXME 6311 SocketAddress addr; 6312 return addr; 6313 } 6314 6315 /// ditto 6316 static SocketAddress abstract_(scope const char[] path) { 6317 char[190] buffer = void; 6318 buffer[0] = 0; 6319 buffer[1 .. path.length] = path[]; 6320 return unix(buffer[0 .. 1 + path.length]); 6321 } 6322 6323 private static SocketAddress getSingleAddress(int family, int flags, scope const char[] address, ushort port) { 6324 addrinfo hints; 6325 hints.ai_family = family; 6326 hints.ai_flags = flags; 6327 6328 char[16] portBuffer; 6329 char[] portString = intToString(port, portBuffer[]); 6330 6331 SocketAddress[1] addr; 6332 auto res = get(address, portString, &hints, provideBuffer(addr[])); 6333 if(res.length == 0) 6334 throw ArsdException!"bad address"(address.idup, port); 6335 return res[0]; 6336 } 6337 6338 /++ 6339 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. 6340 +/ 6341 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 { 6342 addrinfo* res; 6343 CharzBuffer node = nodeName; 6344 CharzBuffer service = serviceOrPort; 6345 auto ret = getaddrinfo(nodeName is null ? null : node.ptr, serviceOrPort is null ? null : service.ptr, hints, &res); 6346 if(ret == 0) { 6347 auto current = res; 6348 while(current) { 6349 if(filter is null || filter(current)) { 6350 SocketAddress addr; 6351 addr.addrlen = cast(socklen_t) current.ai_addrlen; 6352 switch(current.ai_family) { 6353 case AF_INET: 6354 addr.in4 = * cast(sockaddr_in*) current.ai_addr; 6355 break; 6356 case AF_INET6: 6357 addr.in6 = * cast(sockaddr_in6*) current.ai_addr; 6358 break; 6359 case AF_UNIX: 6360 addr.unix_address = * cast(sockaddr_un*) current.ai_addr; 6361 break; 6362 default: 6363 // skip 6364 } 6365 6366 if(!buffer.append(addr)) 6367 break; 6368 } 6369 6370 current = current.ai_next; 6371 } 6372 6373 freeaddrinfo(res); 6374 } else { 6375 version(Windows) { 6376 throw new WindowsApiException("getaddrinfo", ret); 6377 } else { 6378 const char* error = gai_strerror(ret); 6379 } 6380 } 6381 6382 return buffer.slice; 6383 } 6384 6385 /++ 6386 Returns a string representation of the address that identifies it in a custom format. 6387 6388 $(LIST 6389 * 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". 6390 6391 * IPv4 addresses are written in dotted decimal followed by a colon and the port number. For example, "127.0.0.1:8080". 6392 6393 * 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". 6394 ) 6395 +/ 6396 string toString() const @trusted { 6397 char[200] buffer; 6398 switch(address.sa_family) { 6399 case AF_INET: 6400 auto writable = stringz(inet_ntop(address.sa_family, &in4.sin_addr, buffer.ptr, buffer.length)); 6401 auto it = writable.borrow; 6402 buffer[it.length] = ':'; 6403 auto numbers = intToString(port, buffer[it.length + 1 .. $]); 6404 return buffer[0 .. it.length + 1 + numbers.length].idup; 6405 case AF_INET6: 6406 buffer[0] = '['; 6407 auto writable = stringz(inet_ntop(address.sa_family, &in6.sin6_addr, buffer.ptr + 1, buffer.length - 1)); 6408 auto it = writable.borrow; 6409 buffer[it.length + 1] = ']'; 6410 buffer[it.length + 2] = ':'; 6411 auto numbers = intToString(port, buffer[it.length + 3 .. $]); 6412 return buffer[0 .. it.length + 3 + numbers.length].idup; 6413 case AF_UNIX: 6414 // FIXME: it might be abstract in which case stringz is wrong!!!!! 6415 auto writable = stringz(cast(char*) unix_address.sun_path.ptr).borrow; 6416 if(writable.length == 0) 6417 return "unix:"; 6418 string prefix = writable[0] == 0 ? "abstract:" : "unix:"; 6419 buffer[0 .. prefix.length] = prefix[]; 6420 buffer[prefix.length .. prefix.length + writable.length] = writable[writable[0] == 0 ? 1 : 0 .. $]; 6421 return buffer.idup; 6422 case AF_UNSPEC: 6423 return "<unspecified address>"; 6424 default: 6425 return "<unsupported address>"; // FIXME 6426 } 6427 } 6428 6429 ushort port() const @trusted { 6430 switch(address.sa_family) { 6431 case AF_INET: 6432 return ntohs(in4.sin_port); 6433 case AF_INET6: 6434 return ntohs(in6.sin6_port); 6435 default: 6436 return 0; 6437 } 6438 } 6439 6440 /+ 6441 @safe unittest { 6442 SocketAddress[4] buffer; 6443 foreach(addr; SocketAddress.get("arsdnet.net", "http", null, provideBuffer(buffer[]))) 6444 writeln(addr.toString()); 6445 } 6446 +/ 6447 6448 /+ 6449 unittest { 6450 // writeln(SocketAddress.ip4(null, 4444, true)); 6451 // writeln(SocketAddress.ip4("400.3.2.1", 4444)); 6452 // writeln(SocketAddress.ip4("bar", 4444)); 6453 foreach(addr; localhost(4444)) 6454 writeln(addr.toString()); 6455 } 6456 +/ 6457 6458 socklen_t addrlen = typeof(this).sizeof - socklen_t.sizeof; // the size of the union below 6459 6460 union { 6461 sockaddr address; 6462 6463 sockaddr_storage storage; 6464 6465 sockaddr_in in4; 6466 sockaddr_in6 in6; 6467 6468 sockaddr_un unix_address; 6469 } 6470 6471 /+ 6472 this(string node, string serviceOrPort, int family = 0) { 6473 // need to populate the approrpiate address and the length and make sure you set sa_family 6474 } 6475 +/ 6476 6477 int domain() { 6478 return address.sa_family; 6479 } 6480 sockaddr* rawAddr() return { 6481 return &address; 6482 } 6483 socklen_t rawAddrLength() { 6484 return addrlen; 6485 } 6486 6487 // FIXME it is AF_BLUETOOTH 6488 // see: https://people.csail.mit.edu/albert/bluez-intro/x79.html 6489 // see: https://learn.microsoft.com/en-us/windows/win32/Bluetooth/bluetooth-programming-with-windows-sockets 6490 } 6491 6492 private version(Windows) { 6493 struct sockaddr_un { 6494 ushort sun_family; 6495 char[108] sun_path; 6496 } 6497 } 6498 6499 version(HasFile) class AsyncSocket : AsyncFile { 6500 // otherwise: accept, bind, connect, shutdown, close. 6501 6502 static auto lastError() { 6503 version(Windows) 6504 return WSAGetLastError(); 6505 else 6506 return errno; 6507 } 6508 6509 static bool wouldHaveBlocked() { 6510 auto error = lastError; 6511 version(Windows) { 6512 return error == WSAEWOULDBLOCK || error == WSAETIMEDOUT; 6513 } else { 6514 return error == EAGAIN || error == EWOULDBLOCK; 6515 } 6516 } 6517 6518 version(Windows) 6519 enum INVALID = INVALID_SOCKET; 6520 else 6521 enum INVALID = -1; 6522 6523 // type is mostly SOCK_STREAM or SOCK_DGRAM 6524 /++ 6525 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: 6526 6527 --- 6528 auto socket = new Socket(address, Socket.Type.Stream); 6529 socket.connect(address).waitForCompletion(); 6530 --- 6531 +/ 6532 this(SocketAddress address, int type, int protocol = 0) { 6533 // need to look up these values for linux 6534 // type |= SOCK_NONBLOCK | SOCK_CLOEXEC; 6535 6536 handle_ = socket(address.domain(), type, protocol); 6537 if(handle == INVALID) 6538 throw new SystemApiException("socket", lastError()); 6539 6540 super(cast(NativeFileHandle) handle); // I think that cast is ok on Windows... i think 6541 6542 version(Posix) { 6543 makeNonBlocking(handle); 6544 setCloExec(handle); 6545 } 6546 6547 if(address.domain == AF_INET6) { 6548 int opt = 1; 6549 setsockopt(handle, IPPROTO_IPV6 /*SOL_IPV6*/, IPV6_V6ONLY, &opt, opt.sizeof); 6550 } 6551 6552 // FIXME: chekc for broadcast 6553 6554 // FIXME: REUSEADDR ? 6555 6556 // FIXME: also set NO_DELAY prolly 6557 // int opt = 1; 6558 // setsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &opt, opt.sizeof); 6559 } 6560 6561 /++ 6562 Enabling NODELAY can give latency improvements if you are managing buffers on your end 6563 +/ 6564 void setNoDelay(bool enabled) { 6565 6566 } 6567 6568 /++ 6569 6570 `allowQuickRestart` will set the SO_REUSEADDR on unix and SO_DONTLINGER on Windows, 6571 allowing the application to be quickly restarted despite there still potentially being 6572 pending data in the tcp stack. 6573 6574 See https://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux for more information. 6575 6576 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`. 6577 +/ 6578 void bind(SocketAddress address, bool allowQuickRestart = false) { 6579 if(allowQuickRestart) { 6580 // FIXME 6581 } 6582 6583 auto ret = .bind(handle, address.rawAddr, address.rawAddrLength); 6584 if(ret == -1) 6585 throw new SystemApiException("bind", lastError); 6586 } 6587 6588 /++ 6589 You must call [bind] before this. 6590 6591 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. 6592 6593 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). 6594 +/ 6595 void listen(int backlog) { 6596 auto ret = .listen(handle, backlog); 6597 if(ret == -1) 6598 throw new SystemApiException("listen", lastError); 6599 } 6600 6601 /++ 6602 +/ 6603 void shutdown(int how) { 6604 auto ret = .shutdown(handle, how); 6605 if(ret == -1) 6606 throw new SystemApiException("shutdown", lastError); 6607 } 6608 6609 /++ 6610 +/ 6611 override void close() { 6612 version(Windows) 6613 closesocket(handle); 6614 else 6615 .close(handle); 6616 handle_ = -1; 6617 } 6618 6619 /++ 6620 You can also construct your own request externally to control the memory more. 6621 +/ 6622 AsyncConnectRequest connect(SocketAddress address, ubyte[] bufferToSend = null) { 6623 return new AsyncConnectRequest(this, address, bufferToSend); 6624 } 6625 6626 /++ 6627 You can also construct your own request externally to control the memory more. 6628 +/ 6629 AsyncAcceptRequest accept() { 6630 return new AsyncAcceptRequest(this); 6631 } 6632 6633 // note that send is just sendto w/ a null address 6634 // and receive is just receivefrom w/ a null address 6635 /++ 6636 You can also construct your own request externally to control the memory more. 6637 +/ 6638 AsyncSendRequest send(const(ubyte)[] buffer, int flags = 0) { 6639 return new AsyncSendRequest(this, buffer, null, flags); 6640 } 6641 6642 /++ 6643 You can also construct your own request externally to control the memory more. 6644 +/ 6645 AsyncReceiveRequest receive(ubyte[] buffer, int flags = 0) { 6646 return new AsyncReceiveRequest(this, buffer, null, flags); 6647 } 6648 6649 /++ 6650 You can also construct your own request externally to control the memory more. 6651 +/ 6652 AsyncSendRequest sendTo(const(ubyte)[] buffer, SocketAddress* address, int flags = 0) { 6653 return new AsyncSendRequest(this, buffer, address, flags); 6654 } 6655 /++ 6656 You can also construct your own request externally to control the memory more. 6657 +/ 6658 AsyncReceiveRequest receiveFrom(ubyte[] buffer, SocketAddress* address, int flags = 0) { 6659 return new AsyncReceiveRequest(this, buffer, address, flags); 6660 } 6661 6662 /++ 6663 +/ 6664 SocketAddress localAddress() { 6665 SocketAddress addr; 6666 getsockname(handle, &addr.address, &addr.addrlen); 6667 return addr; 6668 } 6669 /++ 6670 +/ 6671 SocketAddress peerAddress() { 6672 SocketAddress addr; 6673 getpeername(handle, &addr.address, &addr.addrlen); 6674 return addr; 6675 } 6676 6677 // for unix sockets on unix only: send/receive fd, get peer creds 6678 6679 /++ 6680 6681 +/ 6682 final NativeSocketHandle handle() { 6683 return handle_; 6684 } 6685 6686 private NativeSocketHandle handle_; 6687 } 6688 6689 /++ 6690 Initiates a connection request and optionally sends initial data as soon as possible. 6691 6692 Calls `ConnectEx` on Windows and emulates it on other systems. 6693 6694 The entire buffer is sent before the operation is considered complete. 6695 6696 NOT IMPLEMENTED / NOT STABLE 6697 +/ 6698 version(HasSocket) class AsyncConnectRequest : AsyncOperationRequest { 6699 // FIXME: i should take a list of addresses and take the first one that succeeds, so a getaddrinfo can be sent straight in. 6700 this(AsyncSocket socket, SocketAddress address, ubyte[] dataToWrite) { 6701 6702 } 6703 6704 override void start() {} 6705 override void cancel() {} 6706 override bool isComplete() { return true; } 6707 override AsyncConnectResponse waitForCompletion() { assert(0); } 6708 } 6709 /++ 6710 +/ 6711 version(HasSocket) class AsyncConnectResponse : AsyncOperationResponse { 6712 const SystemErrorCode errorCode; 6713 6714 this(SystemErrorCode errorCode) { 6715 this.errorCode = errorCode; 6716 } 6717 6718 override bool wasSuccessful() { 6719 return errorCode.wasSuccessful; 6720 } 6721 6722 } 6723 6724 // FIXME: TransmitFile/sendfile support 6725 6726 /++ 6727 Calls `AcceptEx` on Windows and emulates it on other systems. 6728 6729 NOT IMPLEMENTED / NOT STABLE 6730 +/ 6731 version(HasSocket) class AsyncAcceptRequest : AsyncOperationRequest { 6732 AsyncSocket socket; 6733 6734 override void start() {} 6735 override void cancel() {} 6736 override bool isComplete() { return true; } 6737 override AsyncConnectResponse waitForCompletion() { assert(0); } 6738 6739 6740 struct LowLevelOperation { 6741 AsyncSocket file; 6742 ubyte[] buffer; 6743 SocketAddress* address; 6744 6745 this(typeof(this.tupleof) args) { 6746 this.tupleof = args; 6747 } 6748 6749 version(Windows) { 6750 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 6751 WSABUF buf; 6752 buf.len = cast(int) buffer.length; 6753 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 6754 6755 uint flags; 6756 6757 if(address is null) 6758 return WSARecv(file.handle, &buf, 1, null, &flags, overlapped, ocr); 6759 else { 6760 return WSARecvFrom(file.handle, &buf, 1, null, &flags, &(address.address), &(address.addrlen), overlapped, ocr); 6761 } 6762 } 6763 } else { 6764 auto opCall() { 6765 int flags; 6766 if(address is null) 6767 return core.sys.posix.sys.socket.recv(file.handle, buffer.ptr, buffer.length, flags); 6768 else 6769 return core.sys.posix.sys.socket.recvfrom(file.handle, buffer.ptr, buffer.length, flags, &(address.address), &(address.addrlen)); 6770 } 6771 } 6772 6773 string errorString() { 6774 return "Receive"; 6775 } 6776 } 6777 mixin OverlappedIoRequest!(AsyncAcceptResponse, LowLevelOperation); 6778 6779 this(AsyncSocket socket, ubyte[] buffer = null, SocketAddress* address = null) { 6780 llo = LowLevelOperation(socket, buffer, address); 6781 this.response = typeof(this.response).defaultConstructed; 6782 } 6783 6784 // can also look up the local address 6785 } 6786 /++ 6787 +/ 6788 version(HasSocket) class AsyncAcceptResponse : AsyncOperationResponse { 6789 AsyncSocket newSocket; 6790 const SystemErrorCode errorCode; 6791 6792 this(SystemErrorCode errorCode, ubyte[] buffer) { 6793 this.errorCode = errorCode; 6794 } 6795 6796 this(AsyncSocket newSocket, SystemErrorCode errorCode) { 6797 this.newSocket = newSocket; 6798 this.errorCode = errorCode; 6799 } 6800 6801 override bool wasSuccessful() { 6802 return errorCode.wasSuccessful; 6803 } 6804 } 6805 6806 /++ 6807 +/ 6808 version(HasSocket) class AsyncReceiveRequest : AsyncOperationRequest { 6809 struct LowLevelOperation { 6810 AsyncSocket file; 6811 ubyte[] buffer; 6812 int flags; 6813 SocketAddress* address; 6814 6815 this(typeof(this.tupleof) args) { 6816 this.tupleof = args; 6817 } 6818 6819 version(Windows) { 6820 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 6821 WSABUF buf; 6822 buf.len = cast(int) buffer.length; 6823 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 6824 6825 uint flags = this.flags; 6826 6827 if(address is null) 6828 return WSARecv(file.handle, &buf, 1, null, &flags, overlapped, ocr); 6829 else { 6830 return WSARecvFrom(file.handle, &buf, 1, null, &flags, &(address.address), &(address.addrlen), overlapped, ocr); 6831 } 6832 } 6833 } else { 6834 auto opCall() { 6835 if(address is null) 6836 return core.sys.posix.sys.socket.recv(file.handle, buffer.ptr, buffer.length, flags); 6837 else 6838 return core.sys.posix.sys.socket.recvfrom(file.handle, buffer.ptr, buffer.length, flags, &(address.address), &(address.addrlen)); 6839 } 6840 } 6841 6842 string errorString() { 6843 return "Receive"; 6844 } 6845 } 6846 mixin OverlappedIoRequest!(AsyncReceiveResponse, LowLevelOperation); 6847 6848 this(AsyncSocket socket, ubyte[] buffer, SocketAddress* address, int flags) { 6849 llo = LowLevelOperation(socket, buffer, flags, address); 6850 this.response = typeof(this.response).defaultConstructed; 6851 } 6852 6853 } 6854 /++ 6855 +/ 6856 version(HasSocket) class AsyncReceiveResponse : AsyncOperationResponse { 6857 const ubyte[] bufferWritten; 6858 const SystemErrorCode errorCode; 6859 6860 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 6861 this.errorCode = errorCode; 6862 this.bufferWritten = bufferWritten; 6863 } 6864 6865 override bool wasSuccessful() { 6866 return errorCode.wasSuccessful; 6867 } 6868 } 6869 6870 /++ 6871 +/ 6872 version(HasSocket) class AsyncSendRequest : AsyncOperationRequest { 6873 struct LowLevelOperation { 6874 AsyncSocket file; 6875 const(ubyte)[] buffer; 6876 int flags; 6877 SocketAddress* address; 6878 6879 this(typeof(this.tupleof) args) { 6880 this.tupleof = args; 6881 } 6882 6883 version(Windows) { 6884 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 6885 WSABUF buf; 6886 buf.len = cast(int) buffer.length; 6887 buf.buf = cast(typeof(buf.buf)) buffer.ptr; 6888 6889 if(address is null) 6890 return WSASend(file.handle, &buf, 1, null, flags, overlapped, ocr); 6891 else { 6892 return WSASendTo(file.handle, &buf, 1, null, flags, address.rawAddr, address.rawAddrLength, overlapped, ocr); 6893 } 6894 } 6895 } else { 6896 auto opCall() { 6897 if(address is null) 6898 return core.sys.posix.sys.socket.send(file.handle, buffer.ptr, buffer.length, flags); 6899 else 6900 return core.sys.posix.sys.socket.sendto(file.handle, buffer.ptr, buffer.length, flags, address.rawAddr, address.rawAddrLength); 6901 } 6902 } 6903 6904 string errorString() { 6905 return "Send"; 6906 } 6907 } 6908 mixin OverlappedIoRequest!(AsyncSendResponse, LowLevelOperation); 6909 6910 this(AsyncSocket socket, const(ubyte)[] buffer, SocketAddress* address, int flags) { 6911 llo = LowLevelOperation(socket, buffer, flags, address); 6912 this.response = typeof(this.response).defaultConstructed; 6913 } 6914 } 6915 6916 /++ 6917 +/ 6918 version(HasSocket) class AsyncSendResponse : AsyncOperationResponse { 6919 const ubyte[] bufferWritten; 6920 const SystemErrorCode errorCode; 6921 6922 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 6923 this.errorCode = errorCode; 6924 this.bufferWritten = bufferWritten; 6925 } 6926 6927 override bool wasSuccessful() { 6928 return errorCode.wasSuccessful; 6929 } 6930 6931 } 6932 6933 /++ 6934 A set of sockets bound and ready to accept connections on worker threads. 6935 6936 Depending on the specified address, it can be tcp, tcpv6, unix domain, or all of the above. 6937 6938 NOT IMPLEMENTED / NOT STABLE 6939 +/ 6940 version(HasSocket) class StreamServer { 6941 AsyncSocket[] sockets; 6942 6943 this(SocketAddress[] listenTo, int backlog = 8) { 6944 foreach(listen; listenTo) { 6945 auto socket = new AsyncSocket(listen, SOCK_STREAM); 6946 6947 // FIXME: allInterfaces for ipv6 also covers ipv4 so the bind can fail... 6948 // so we have to permit it to fail w/ address in use if we know we already 6949 // are listening to ipv6 6950 6951 // or there is a setsockopt ipv6 only thing i could set. 6952 6953 socket.bind(listen); 6954 socket.listen(backlog); 6955 sockets ~= socket; 6956 6957 // writeln(socket.localAddress.port); 6958 } 6959 6960 // i have to start accepting on each thread for each socket... 6961 } 6962 // when a new connection arrives, it calls your callback 6963 // can be on a specific thread or on any thread 6964 6965 6966 void start() { 6967 foreach(socket; sockets) { 6968 auto request = socket.accept(); 6969 request.start(); 6970 } 6971 } 6972 } 6973 6974 /+ 6975 unittest { 6976 auto ss = new StreamServer(SocketAddress.localhost(0)); 6977 } 6978 +/ 6979 6980 /++ 6981 A socket bound and ready to use receiveFrom 6982 6983 Depending on the address, it can be udp or unix domain. 6984 6985 NOT IMPLEMENTED / NOT STABLE 6986 +/ 6987 version(HasSocket) class DatagramListener { 6988 // whenever a udp message arrives, it calls your callback 6989 // can be on a specific thread or on any thread 6990 6991 // UDP is realistically just an async read on the bound socket 6992 // just it can get the "from" data out and might need the "more in packet" flag 6993 } 6994 6995 /++ 6996 Just in case I decide to change the implementation some day. 6997 +/ 6998 version(HasFile) alias AsyncAnonymousPipe = AsyncFile; 6999 7000 7001 // AsyncAnonymousPipe connectNamedPipe(AsyncAnonymousPipe preallocated, string name) 7002 7003 // 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. 7004 7005 // DIRECTORY LISTINGS 7006 // not async, so if you want that, do it in a helper thread 7007 // just a convenient function to have (tho phobos has a decent one too, importing it expensive af) 7008 7009 /++ 7010 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. 7011 7012 History: 7013 previously in minigui as a private function. Moved to arsd.core on April 3, 2023 7014 +/ 7015 version(HasFile) GetFilesResult getFiles(string directory, scope void delegate(string name, bool isDirectory) dg) { 7016 // FIXME: my buffers here aren't great lol 7017 7018 SavedArgument[1] argsForException() { 7019 return [ 7020 SavedArgument("directory", LimitedVariant(directory)), 7021 ]; 7022 } 7023 7024 version(Windows) { 7025 WIN32_FIND_DATA data; 7026 // FIXME: if directory ends with / or \\ ? 7027 WCharzBuffer search = WCharzBuffer(directory ~ "/*"); 7028 auto handle = FindFirstFileW(search.ptr, &data); 7029 scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle); 7030 if(handle is INVALID_HANDLE_VALUE) { 7031 if(GetLastError() == ERROR_FILE_NOT_FOUND) 7032 return GetFilesResult.fileNotFound; 7033 throw new WindowsApiException("FindFirstFileW", GetLastError(), argsForException()[]); 7034 } 7035 7036 try_more: 7037 7038 string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]); 7039 7040 /+ 7041 FILETIME ftLastWriteTime; 7042 DWORD nFileSizeHigh; 7043 DWORD nFileSizeLow; 7044 7045 but these not available on linux w/o statting each file! 7046 +/ 7047 7048 dg(name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false); 7049 7050 auto ret = FindNextFileW(handle, &data); 7051 if(ret == 0) { 7052 if(GetLastError() == ERROR_NO_MORE_FILES) 7053 return GetFilesResult.success; 7054 throw new WindowsApiException("FindNextFileW", GetLastError(), argsForException()[]); 7055 } 7056 7057 goto try_more; 7058 7059 } else version(Posix) { 7060 import core.sys.posix.dirent; 7061 import core.stdc.errno; 7062 auto dir = opendir((directory ~ "\0").ptr); 7063 scope(exit) 7064 if(dir) closedir(dir); 7065 if(dir is null) 7066 throw new ErrnoApiException("opendir", errno, argsForException()); 7067 7068 auto dirent = readdir(dir); 7069 if(dirent is null) 7070 return GetFilesResult.fileNotFound; 7071 7072 try_more: 7073 7074 string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup; 7075 7076 dg(name, dirent.d_type == DT_DIR); 7077 7078 dirent = readdir(dir); 7079 if(dirent is null) 7080 return GetFilesResult.success; 7081 7082 goto try_more; 7083 } else static assert(0); 7084 } 7085 7086 /// ditto 7087 enum GetFilesResult { 7088 success, 7089 fileNotFound 7090 } 7091 7092 /++ 7093 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. 7094 7095 More things may be added later to be more like what Phobos supports. 7096 +/ 7097 bool matchesFilePattern(scope const(char)[] name, scope const(char)[] pattern, char star = '*') { 7098 if(pattern.length == 0) 7099 return false; 7100 if(pattern.length == 1 && pattern[0] == star) 7101 return true; 7102 if(pattern.length > 2 && pattern[0] == star && pattern[$-1] == star) { 7103 // if the rest of pattern appears in name, it is good 7104 return name.indexOf(pattern[1 .. $-1]) != -1; 7105 } else if(pattern[0] == star) { 7106 // if the rest of pattern is at end of name, it is good 7107 return name.endsWith(pattern[1 .. $]); 7108 } else if(pattern[$-1] == star) { 7109 // if the rest of pattern is at start of name, it is good 7110 return name.startsWith(pattern[0 .. $-1]); 7111 } else if(pattern.length >= 3) { 7112 char[1] starString = star; 7113 auto idx = pattern.indexOf(starString[]); 7114 if(idx != -1) { 7115 auto lhs = pattern[0 .. idx]; 7116 auto rhs = pattern[idx + 1 .. $]; 7117 if(name.length >= lhs.length + rhs.length) { 7118 return name.startsWith(lhs) && name.endsWith(rhs); 7119 } else { 7120 return false; 7121 } 7122 } 7123 } 7124 7125 return name == pattern; 7126 } 7127 7128 unittest { 7129 assert("test.html".matchesFilePattern("*")); 7130 assert("test.html".matchesFilePattern("*.html")); 7131 assert("test.html".matchesFilePattern("*.*")); 7132 assert("test.html".matchesFilePattern("test.*")); 7133 assert(!"test.html".matchesFilePattern("pest.*")); 7134 assert(!"test.html".matchesFilePattern("*.dhtml")); 7135 7136 assert("test.html".matchesFilePattern("t*.html")); 7137 assert(!"test.html".matchesFilePattern("e*.html")); 7138 } 7139 7140 package(arsd) int indexOf(scope const(char)[] haystack, scope const(char)[] needle) { 7141 if(haystack.length < needle.length) 7142 return -1; 7143 if(haystack == needle) 7144 return 0; 7145 foreach(i; 0 .. haystack.length - needle.length + 1) 7146 if(haystack[i .. i + needle.length] == needle) 7147 return cast(int) i; 7148 return -1; 7149 } 7150 7151 package(arsd) int indexOf(scope const(ubyte)[] haystack, scope const(char)[] needle) { 7152 return indexOf(cast(const(char)[]) haystack, needle); 7153 } 7154 7155 unittest { 7156 assert("foo".indexOf("f") == 0); 7157 assert("foo".indexOf("o") == 1); 7158 assert("foo".indexOf("foo") == 0); 7159 assert("foo".indexOf("oo") == 1); 7160 assert("foo".indexOf("fo") == 0); 7161 assert("foo".indexOf("boo") == -1); 7162 assert("foo".indexOf("food") == -1); 7163 } 7164 7165 package(arsd) bool endsWith(scope const(char)[] haystack, scope const(char)[] needle) { 7166 if(needle.length > haystack.length) 7167 return false; 7168 return haystack[$ - needle.length .. $] == needle; 7169 } 7170 7171 unittest { 7172 assert("foo".endsWith("o")); 7173 assert("foo".endsWith("oo")); 7174 assert("foo".endsWith("foo")); 7175 assert(!"foo".endsWith("food")); 7176 assert(!"foo".endsWith("d")); 7177 } 7178 7179 package(arsd) bool startsWith(scope const(char)[] haystack, scope const(char)[] needle) { 7180 if(needle.length > haystack.length) 7181 return false; 7182 return haystack[0 .. needle.length] == needle; 7183 } 7184 7185 unittest { 7186 assert("foo".startsWith("f")); 7187 assert("foo".startsWith("fo")); 7188 assert("foo".startsWith("foo")); 7189 assert(!"foo".startsWith("food")); 7190 assert(!"foo".startsWith("d")); 7191 } 7192 7193 7194 // FILE/DIR WATCHES 7195 // linux does it by name, windows and bsd do it by handle/descriptor 7196 // dispatches change event to either your thread or maybe the any task` queue. 7197 7198 /++ 7199 PARTIALLY IMPLEMENTED / NOT STABLE 7200 7201 +/ 7202 class DirectoryWatcher { 7203 private { 7204 version(Arsd_core_windows) { 7205 OVERLAPPED overlapped; 7206 HANDLE hDirectory; 7207 ubyte[] buffer; 7208 7209 extern(Windows) 7210 static void overlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) @system { 7211 typeof(this) rr = cast(typeof(this)) (cast(void*) lpOverlapped - typeof(this).overlapped.offsetof); 7212 7213 // dwErrorCode 7214 auto response = rr.buffer[0 .. dwNumberOfBytesTransferred]; 7215 7216 while(response.length) { 7217 auto fni = cast(FILE_NOTIFY_INFORMATION*) response.ptr; 7218 auto filename = fni.FileName[0 .. fni.FileNameLength]; 7219 7220 if(fni.NextEntryOffset) 7221 response = response[fni.NextEntryOffset .. $]; 7222 else 7223 response = response[$..$]; 7224 7225 // FIXME: I think I need to pin every overlapped op while it is pending 7226 // and unpin it when it is returned. GC.addRoot... but i don't wanna do that 7227 // every op so i guess i should do a refcount scheme similar to the other callback helper. 7228 7229 rr.changeHandler( 7230 FilePath(makeUtf8StringFromWindowsString(filename)), // FIXME: this is a relative path 7231 ChangeOperation.unknown // FIXME this is fni.Action 7232 ); 7233 } 7234 7235 rr.requestRead(); 7236 } 7237 7238 void requestRead() { 7239 DWORD ignored; 7240 if(!ReadDirectoryChangesW( 7241 hDirectory, 7242 buffer.ptr, 7243 cast(int) buffer.length, 7244 recursive, 7245 FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_FILE_NAME, 7246 &ignored, 7247 &overlapped, 7248 &overlappedCompletionRoutine 7249 )) { 7250 auto error = GetLastError(); 7251 /+ 7252 if(error == ERROR_IO_PENDING) { 7253 // not expected here, the docs say it returns true when queued 7254 } 7255 +/ 7256 7257 throw new SystemApiException("ReadDirectoryChangesW", error); 7258 } 7259 } 7260 } else version(Arsd_core_epoll) { 7261 static int inotifyfd = -1; // this is TLS since it is associated with the thread's event loop 7262 static ICoreEventLoop.UnregisterToken inotifyToken; 7263 static CallbackHelper inotifycb; 7264 static DirectoryWatcher[int] watchMappings; 7265 7266 static ~this() { 7267 if(inotifyfd != -1) { 7268 close(inotifyfd); 7269 inotifyfd = -1; 7270 } 7271 } 7272 7273 import core.sys.linux.sys.inotify; 7274 7275 int watchId = -1; 7276 7277 static void inotifyReady() { 7278 // read from it 7279 ubyte[256 /* NAME_MAX + 1 */ + inotify_event.sizeof] sbuffer; 7280 7281 auto ret = read(inotifyfd, sbuffer.ptr, sbuffer.length); 7282 if(ret == -1) { 7283 auto errno = errno; 7284 if(errno == EAGAIN || errno == EWOULDBLOCK) 7285 return; 7286 throw new SystemApiException("read inotify", errno); 7287 } else if(ret == 0) { 7288 assert(0, "I don't think this is ever supposed to happen"); 7289 } 7290 7291 auto buffer = sbuffer[0 .. ret]; 7292 7293 while(buffer.length > 0) { 7294 inotify_event* event = cast(inotify_event*) buffer.ptr; 7295 buffer = buffer[inotify_event.sizeof .. $]; 7296 char[] filename = cast(char[]) buffer[0 .. event.len]; 7297 buffer = buffer[event.len .. $]; 7298 7299 // note that filename is padded with zeroes, so it is actually a stringz 7300 7301 if(auto obj = event.wd in watchMappings) { 7302 (*obj).changeHandler( 7303 FilePath(stringz(filename.ptr).borrow.idup), // FIXME: this is a relative path 7304 ChangeOperation.unknown // FIXME 7305 ); 7306 } else { 7307 // it has probably already been removed 7308 } 7309 } 7310 } 7311 } else version(Arsd_core_kqueue) { 7312 int fd; 7313 CallbackHelper cb; 7314 } 7315 7316 FilePath path; 7317 string globPattern; 7318 bool recursive; 7319 void delegate(FilePath filename, ChangeOperation op) changeHandler; 7320 } 7321 7322 enum ChangeOperation { 7323 unknown, 7324 deleted, // NOTE_DELETE, IN_DELETE, FILE_NOTIFY_CHANGE_FILE_NAME 7325 written, // NOTE_WRITE / NOTE_EXTEND / NOTE_TRUNCATE, IN_MODIFY, FILE_NOTIFY_CHANGE_LAST_WRITE / FILE_NOTIFY_CHANGE_SIZE 7326 renamed, // NOTE_RENAME, the moved from/to in linux, FILE_NOTIFY_CHANGE_FILE_NAME 7327 metadataChanged // NOTE_ATTRIB, IN_ATTRIB, FILE_NOTIFY_CHANGE_ATTRIBUTES 7328 7329 // 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. 7330 } 7331 7332 /+ 7333 Windows and Linux work best when you watch directories. The operating system tells you the name of files as they change. 7334 7335 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. 7336 7337 inotify is kinda clearly the best of the bunch, with Windows in second place, and kqueue dead last. 7338 7339 7340 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. 7341 7342 If a path is a file, it only signals when that specific file is written. This is most efficient on BSD. 7343 7344 7345 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. 7346 +/ 7347 7348 /++ 7349 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. 7350 7351 On Windows, the globPattern is just used to filter events. 7352 7353 On Linux, the `recursive` flag, if set, will cause it to add additional OS-level watches for each subdirectory. 7354 7355 On BSD, anything other than a null pattern will cause a directory scan to add files to the watch list. 7356 7357 For best results, use the most limited thing you need, as watches can get quite involved on the bsd systems. 7358 7359 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. 7360 7361 If the event queue is too busy, the OS may skip a notification. 7362 7363 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. 7364 +/ 7365 this(FilePath directoryToWatch, string globPattern, bool recursive, void delegate(FilePath pathModified, ChangeOperation op) dg) { 7366 this.path = directoryToWatch; 7367 this.globPattern = globPattern; 7368 this.recursive = recursive; 7369 this.changeHandler = dg; 7370 7371 version(Arsd_core_windows) { 7372 WCharzBuffer wname = directoryToWatch.path; 7373 buffer = new ubyte[](1024); 7374 hDirectory = CreateFileW( 7375 wname.ptr, 7376 GENERIC_READ, 7377 FILE_SHARE_READ, 7378 null, 7379 OPEN_EXISTING, 7380 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_BACKUP_SEMANTICS, 7381 null 7382 ); 7383 if(hDirectory == INVALID_HANDLE_VALUE) 7384 throw new SystemApiException("CreateFileW", GetLastError()); 7385 7386 requestRead(); 7387 } else version(Arsd_core_epoll) { 7388 auto el = getThisThreadEventLoop(); 7389 7390 // no need for sync because it is thread-local 7391 if(inotifyfd == -1) { 7392 inotifyfd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); 7393 if(inotifyfd == -1) 7394 throw new SystemApiException("inotify_init1", errno); 7395 7396 inotifycb = new CallbackHelper(&inotifyReady); 7397 inotifyToken = el.addCallbackOnFdReadable(inotifyfd, inotifycb); 7398 } 7399 7400 uint event_mask = IN_CREATE | IN_MODIFY | IN_DELETE; // FIXME 7401 CharzBuffer dtw = directoryToWatch.path; 7402 auto watchId = inotify_add_watch(inotifyfd, dtw.ptr, event_mask); 7403 if(watchId < -1) 7404 throw new SystemApiException("inotify_add_watch", errno, [SavedArgument("path", LimitedVariant(directoryToWatch.path))]); 7405 7406 watchMappings[watchId] = this; 7407 7408 // FIXME: recursive needs to add child things individually 7409 7410 } else version(Arsd_core_kqueue) { 7411 auto el = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 7412 7413 // FIXME: need to scan for globPattern 7414 // when a new file is added, i'll have to diff my list to detect it and open it too 7415 // and recursive might need to scan down too. 7416 7417 kevent_t ev; 7418 7419 import core.sys.posix.fcntl; 7420 CharzBuffer buffer = CharzBuffer(directoryToWatch.path); 7421 fd = ErrnoEnforce!open(buffer.ptr, O_RDONLY); 7422 setCloExec(fd); 7423 7424 cb = new CallbackHelper(&triggered); 7425 7426 EV_SET(&ev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE, 0, cast(void*) cb); 7427 ErrnoEnforce!kevent(el.kqueuefd, &ev, 1, null, 0, null); 7428 } else assert(0, "Not yet implemented for this platform"); 7429 } 7430 7431 private void triggered() { 7432 writeln("triggered"); 7433 } 7434 7435 void dispose() { 7436 version(Arsd_core_windows) { 7437 CloseHandle(hDirectory); 7438 } else version(Arsd_core_epoll) { 7439 watchMappings.remove(watchId); // I could also do this on the IN_IGNORE notification but idk 7440 inotify_rm_watch(inotifyfd, watchId); 7441 } else version(Arsd_core_kqueue) { 7442 ErrnoEnforce!close(fd); 7443 fd = -1; 7444 } 7445 } 7446 } 7447 7448 version(none) 7449 void main() { 7450 7451 // auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation, AsyncFile.RequirePreexisting.yes); 7452 7453 /+ 7454 getFiles("c:/windows\\", (string filename, bool isDirectory) { 7455 writeln(filename, " ", isDirectory ? "[dir]": "[file]"); 7456 }); 7457 +/ 7458 7459 auto w = new DirectoryWatcher(FilePath("."), "*", false, (path, op) { 7460 writeln(path.path); 7461 }); 7462 getThisThreadEventLoop().run(() => false); 7463 } 7464 7465 /++ 7466 This starts up a local pipe. If it is already claimed, it just communicates with the existing one through the interface. 7467 +/ 7468 class SingleInstanceApplication { 7469 // FIXME 7470 } 7471 7472 version(none) 7473 void main() { 7474 7475 auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation, AsyncFile.RequirePreexisting.yes); 7476 7477 auto buffer = cast(ubyte[]) "hello"; 7478 auto wr = new AsyncWriteRequest(file, buffer, 0); 7479 wr.start(); 7480 7481 wr.waitForCompletion(); 7482 7483 file.close(); 7484 } 7485 7486 /++ 7487 Implementation details of some requests. You shouldn't need to know any of this, the interface is all public. 7488 +/ 7489 mixin template OverlappedIoRequest(Response, LowLevelOperation) { 7490 private { 7491 LowLevelOperation llo; 7492 7493 OwnedClass!Response response; 7494 7495 version(Windows) { 7496 OVERLAPPED overlapped; 7497 7498 extern(Windows) 7499 static void overlappedCompletionRoutine(DWORD dwErrorCode, DWORD dwNumberOfBytesTransferred, LPOVERLAPPED lpOverlapped) @system { 7500 typeof(this) rr = cast(typeof(this)) (cast(void*) lpOverlapped - typeof(this).overlapped.offsetof); 7501 7502 rr.response = typeof(rr.response)(SystemErrorCode(dwErrorCode), rr.llo.buffer[0 .. dwNumberOfBytesTransferred]); 7503 rr.state_ = State.complete; 7504 7505 if(rr.oncomplete) 7506 rr.oncomplete(rr); 7507 7508 // FIXME: on complete? 7509 // 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 7510 } 7511 } 7512 7513 version(Posix) { 7514 ICoreEventLoop.RearmToken eventRegistration; 7515 CallbackHelper cb; 7516 7517 final CallbackHelper getCb() { 7518 if(cb is null) 7519 cb = new CallbackHelper(&cbImpl); 7520 return cb; 7521 } 7522 7523 final void cbImpl() { 7524 // it is ready to complete, time to do it 7525 auto ret = llo(); 7526 markCompleted(ret, errno); 7527 } 7528 7529 void markCompleted(long ret, int errno) { 7530 // maybe i should queue an apc to actually do it, to ensure the event loop has cycled... FIXME 7531 if(ret == -1) 7532 response = typeof(response)(SystemErrorCode(errno), null); 7533 else 7534 response = typeof(response)(SystemErrorCode(0), llo.buffer[0 .. cast(size_t) ret]); 7535 state_ = State.complete; 7536 7537 if(oncomplete) 7538 oncomplete(this); 7539 } 7540 } 7541 } 7542 7543 enum State { 7544 unused, 7545 started, 7546 inProgress, 7547 complete 7548 } 7549 private State state_; 7550 7551 override void start() { 7552 assert(state_ == State.unused); 7553 7554 state_ = State.started; 7555 7556 version(Windows) { 7557 if(llo(&overlapped, &overlappedCompletionRoutine)) { 7558 // all good, though GetLastError() might have some informative info 7559 //writeln(GetLastError()); 7560 } else { 7561 // operation failed, the operation is always ReadFileEx or WriteFileEx so it won't give the io pending thing here 7562 // should i issue error async? idk 7563 state_ = State.complete; 7564 throw new SystemApiException(llo.errorString(), GetLastError()); 7565 } 7566 7567 // 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. 7568 } else version(Posix) { 7569 7570 // first try to just do it 7571 auto ret = llo(); 7572 7573 auto errno = errno; 7574 if(ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { // unable to complete right now, register and try when it is ready 7575 if(eventRegistration is typeof(eventRegistration).init) 7576 eventRegistration = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(this.llo.file.handle, this.getCb); 7577 else 7578 eventRegistration.rearm(); 7579 } else { 7580 // i could set errors sync or async and since it couldn't even start, i think a sync exception is the right way 7581 if(ret == -1) 7582 throw new SystemApiException(llo.errorString(), errno); 7583 markCompleted(ret, errno); // it completed synchronously (if it is an error nor not is handled by the completion handler) 7584 } 7585 } 7586 } 7587 7588 override void cancel() { 7589 if(state_ == State.complete) 7590 return; // it has already finished, just leave it alone, no point discarding what is already done 7591 version(Windows) { 7592 if(state_ != State.unused) 7593 Win32Enforce!CancelIoEx(llo.file.AbstractFile.handle, &overlapped); 7594 // Windows will notify us when the cancellation is complete, so we need to wait for that before updating the state 7595 } else version(Posix) { 7596 if(state_ != State.unused) 7597 eventRegistration.unregister(); 7598 markCompleted(-1, ECANCELED); 7599 } 7600 } 7601 7602 override bool isComplete() { 7603 // just always let the event loop do it instead 7604 return state_ == State.complete; 7605 7606 /+ 7607 version(Windows) { 7608 return HasOverlappedIoCompleted(&overlapped); 7609 } else version(Posix) { 7610 return state_ == State.complete; 7611 7612 } 7613 +/ 7614 } 7615 7616 override Response waitForCompletion() { 7617 if(state_ == State.unused) 7618 start(); 7619 7620 // FIXME: if we are inside a fiber, we can set a oncomplete callback and then yield instead... 7621 if(state_ != State.complete) 7622 getThisThreadEventLoop().run(&isComplete); 7623 7624 /+ 7625 version(Windows) { 7626 SleepEx(INFINITE, true); 7627 7628 //DWORD numberTransferred; 7629 //Win32Enforce!GetOverlappedResult(file.handle, &overlapped, &numberTransferred, true); 7630 } else version(Posix) { 7631 getThisThreadEventLoop().run(&isComplete); 7632 } 7633 +/ 7634 7635 return response; 7636 } 7637 7638 /++ 7639 Repeats the operation, restarting the request. 7640 7641 This must only be called when the operation has already completed. 7642 +/ 7643 void repeat() { 7644 if(state_ != State.complete) 7645 throw new Exception("wrong use, cannot repeat if not complete"); 7646 state_ = State.unused; 7647 start(); 7648 } 7649 7650 void delegate(typeof(this) t) oncomplete; 7651 } 7652 7653 /++ 7654 You can write to a file asynchronously by creating one of these. 7655 +/ 7656 version(HasSocket) final class AsyncWriteRequest : AsyncOperationRequest { 7657 struct LowLevelOperation { 7658 AsyncFile file; 7659 ubyte[] buffer; 7660 long offset; 7661 7662 this(typeof(this.tupleof) args) { 7663 this.tupleof = args; 7664 } 7665 7666 version(Windows) { 7667 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 7668 overlapped.Offset = (cast(ulong) offset) & 0xffff_ffff; 7669 overlapped.OffsetHigh = ((cast(ulong) offset) >> 32) & 0xffff_ffff; 7670 return WriteFileEx(file.handle, buffer.ptr, cast(int) buffer.length, overlapped, ocr); 7671 } 7672 } else { 7673 auto opCall() { 7674 return core.sys.posix.unistd.write(file.handle, buffer.ptr, buffer.length); 7675 } 7676 } 7677 7678 string errorString() { 7679 return "Write"; 7680 } 7681 } 7682 mixin OverlappedIoRequest!(AsyncWriteResponse, LowLevelOperation); 7683 7684 this(AsyncFile file, ubyte[] buffer, long offset) { 7685 this.llo = LowLevelOperation(file, buffer, offset); 7686 response = typeof(response).defaultConstructed; 7687 } 7688 } 7689 7690 /++ 7691 7692 +/ 7693 class AsyncWriteResponse : AsyncOperationResponse { 7694 const ubyte[] bufferWritten; 7695 const SystemErrorCode errorCode; 7696 7697 this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten) { 7698 this.errorCode = errorCode; 7699 this.bufferWritten = bufferWritten; 7700 } 7701 7702 override bool wasSuccessful() { 7703 return errorCode.wasSuccessful; 7704 } 7705 } 7706 7707 // FIXME: on Windows, you may want two operations outstanding at once 7708 // so there's no delay between sequential ops. this system currently makes that 7709 // impossible since epoll won't let you register twice... 7710 7711 // FIXME: if an op completes synchronously, and oncomplete calls repeat 7712 // you can get infinite recursion into the stack... 7713 7714 /++ 7715 7716 +/ 7717 version(HasSocket) final class AsyncReadRequest : AsyncOperationRequest { 7718 struct LowLevelOperation { 7719 AsyncFile file; 7720 ubyte[] buffer; 7721 long offset; 7722 7723 this(typeof(this.tupleof) args) { 7724 this.tupleof = args; 7725 } 7726 7727 version(Windows) { 7728 auto opCall(OVERLAPPED* overlapped, LPOVERLAPPED_COMPLETION_ROUTINE ocr) { 7729 overlapped.Offset = (cast(ulong) offset) & 0xffff_ffff; 7730 overlapped.OffsetHigh = ((cast(ulong) offset) >> 32) & 0xffff_ffff; 7731 return ReadFileEx(file.handle, buffer.ptr, cast(int) buffer.length, overlapped, ocr); 7732 } 7733 } else { 7734 auto opCall() { 7735 return core.sys.posix.unistd.read(file.handle, buffer.ptr, buffer.length); 7736 } 7737 } 7738 7739 string errorString() { 7740 return "Read"; 7741 } 7742 } 7743 mixin OverlappedIoRequest!(AsyncReadResponse, LowLevelOperation); 7744 7745 /++ 7746 The file must have the overlapped flag enabled on Windows and the nonblock flag set on Posix. 7747 7748 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`. 7749 7750 The offset is where to start reading a disk file. For all other types of files, pass 0. 7751 +/ 7752 this(AsyncFile file, ubyte[] buffer, long offset) { 7753 this.llo = LowLevelOperation(file, buffer, offset); 7754 response = typeof(response).defaultConstructed; 7755 } 7756 7757 /++ 7758 7759 +/ 7760 // abstract void repeat(); 7761 } 7762 7763 /++ 7764 7765 +/ 7766 class AsyncReadResponse : AsyncOperationResponse { 7767 const ubyte[] bufferRead; 7768 const SystemErrorCode errorCode; 7769 7770 this(SystemErrorCode errorCode, const(ubyte)[] bufferRead) { 7771 this.errorCode = errorCode; 7772 this.bufferRead = bufferRead; 7773 } 7774 7775 override bool wasSuccessful() { 7776 return errorCode.wasSuccessful; 7777 } 7778 } 7779 7780 /+ 7781 Tasks: 7782 startTask() 7783 startSubTask() - what if it just did this when it knows it is being run from inside a task? 7784 runHelperFunction() - whomever it reports to is the parent 7785 +/ 7786 7787 version(HasThread) class SchedulableTask : Fiber { 7788 private void delegate() dg; 7789 7790 // linked list stuff 7791 private static SchedulableTask taskRoot; 7792 private SchedulableTask previous; 7793 private SchedulableTask next; 7794 7795 // need the controlling thread to know how to wake it up if it receives a message 7796 private Thread controllingThread; 7797 7798 // the api 7799 7800 this(void delegate() dg) { 7801 assert(dg !is null); 7802 7803 this.dg = dg; 7804 super(&taskRunner); 7805 7806 if(taskRoot !is null) { 7807 this.next = taskRoot; 7808 taskRoot.previous = this; 7809 } 7810 taskRoot = this; 7811 } 7812 7813 /+ 7814 enum BehaviorOnCtrlC { 7815 ignore, 7816 cancel, 7817 deliverMessage 7818 } 7819 +/ 7820 7821 private bool cancelled; 7822 7823 public void cancel() { 7824 this.cancelled = true; 7825 // if this is running, we can throw immediately 7826 // otherwise if we're calling from an appropriate thread, we can call it immediately 7827 // otherwise we need to queue a wakeup to its own thread. 7828 // tbh we should prolly just queue it every time 7829 } 7830 7831 private void taskRunner() { 7832 try { 7833 dg(); 7834 } catch(TaskCancelledException tce) { 7835 // this space intentionally left blank; 7836 // the purpose of this exception is to just 7837 // let the fiber's destructors run before we 7838 // let it die. 7839 } catch(Throwable t) { 7840 if(taskUncaughtException is null) { 7841 throw t; 7842 } else { 7843 taskUncaughtException(t); 7844 } 7845 } finally { 7846 if(this is taskRoot) { 7847 taskRoot = taskRoot.next; 7848 if(taskRoot !is null) 7849 taskRoot.previous = null; 7850 } else { 7851 assert(this.previous !is null); 7852 assert(this.previous.next is this); 7853 this.previous.next = this.next; 7854 if(this.next !is null) 7855 this.next.previous = this.previous; 7856 } 7857 } 7858 } 7859 } 7860 7861 /++ 7862 7863 +/ 7864 void delegate(Throwable t) taskUncaughtException; 7865 7866 /++ 7867 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. 7868 7869 --- 7870 if(auto controller = inSchedulableTask()) { 7871 controller.yieldUntilReadable(...); 7872 } 7873 --- 7874 7875 History: 7876 Added August 11, 2023 (dub v11.1) 7877 +/ 7878 version(HasThread) SchedulableTaskController inSchedulableTask() { 7879 import core.thread.fiber; 7880 7881 if(auto fiber = Fiber.getThis) { 7882 return SchedulableTaskController(cast(SchedulableTask) fiber); 7883 } 7884 7885 return SchedulableTaskController(null); 7886 } 7887 7888 /// ditto 7889 version(HasThread) struct SchedulableTaskController { 7890 private this(SchedulableTask fiber) { 7891 this.fiber = fiber; 7892 } 7893 7894 private SchedulableTask fiber; 7895 7896 /++ 7897 7898 +/ 7899 bool opCast(T : bool)() { 7900 return fiber !is null; 7901 } 7902 7903 /++ 7904 7905 +/ 7906 version(Posix) 7907 void yieldUntilReadable(NativeFileHandle handle) { 7908 assert(fiber !is null); 7909 7910 auto cb = new CallbackHelper(() { fiber.call(); }); 7911 7912 // FIXME: if the fd is already registered in this thread it can throw... 7913 version(Windows) 7914 auto rearmToken = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(handle, cb); 7915 else 7916 auto rearmToken = getThisThreadEventLoop().addCallbackOnFdReadableOneShot(handle, cb); 7917 7918 // FIXME: this is only valid if the fiber is only ever going to run in this thread! 7919 fiber.yield(); 7920 7921 rearmToken.unregister(); 7922 7923 // what if there are other messages, like a ctrl+c? 7924 if(fiber.cancelled) 7925 throw new TaskCancelledException(); 7926 } 7927 7928 version(Windows) 7929 void yieldUntilSignaled(NativeFileHandle handle) { 7930 // add it to the WaitForMultipleObjects thing w/ a cb 7931 } 7932 } 7933 7934 class TaskCancelledException : object.Exception { 7935 this() { 7936 super("Task cancelled"); 7937 } 7938 } 7939 7940 /+ 7941 version(HasThread) private class ArsdThread : Thread { 7942 this(void delegate() run) { 7943 this.run = run; 7944 7945 super(&runner); 7946 } 7947 7948 private void delegate() run; 7949 final void runner() { 7950 // FIXME: need to mask most signals so we don't handle things intended for the process as a whole 7951 7952 try { 7953 run(); 7954 7955 // FIXME: post a thread complete notification to the supervisor 7956 // supervisor should join it at that time 7957 } catch(Throwable t) { 7958 // FIXME: post a thread failed notification to the supervisor 7959 // supervisor should join it at that time 7960 throw t; 7961 } 7962 } 7963 } 7964 +/ 7965 7966 version(HasThread) private class CoreWorkerThread : Thread { 7967 this(EventLoopType type) { 7968 this.type = type; 7969 7970 // task runners are supposed to have smallish stacks since they either just run a single callback or call into fibers 7971 // the helper runners might be a bit bigger tho 7972 super(&run); 7973 } 7974 void run() { 7975 eventLoop = getThisThreadEventLoop(this.type); 7976 atomicOp!"+="(startedCount, 1); 7977 atomicOp!"+="(runningCount, 1); 7978 scope(exit) { 7979 atomicOp!"-="(runningCount, 1); 7980 } 7981 7982 eventLoop.run(() => cancelled); 7983 } 7984 7985 private bool cancelled; 7986 7987 void cancel() { 7988 cancelled = true; 7989 } 7990 7991 EventLoopType type; 7992 ICoreEventLoop eventLoop; 7993 7994 __gshared static { 7995 CoreWorkerThread[] taskRunners; 7996 CoreWorkerThread[] helperRunners; 7997 ICoreEventLoop mainThreadLoop; 7998 7999 // for the helper function thing on the bsds i could have my own little circular buffer of availability 8000 8001 shared(int) startedCount; 8002 shared(int) runningCount; 8003 8004 bool started; 8005 8006 void setup(int numberOfTaskRunners, int numberOfHelpers) { 8007 assert(!started); 8008 synchronized { 8009 mainThreadLoop = getThisThreadEventLoop(); 8010 8011 foreach(i; 0 .. numberOfTaskRunners) { 8012 auto nt = new CoreWorkerThread(EventLoopType.TaskRunner); 8013 taskRunners ~= nt; 8014 nt.start(); 8015 } 8016 foreach(i; 0 .. numberOfHelpers) { 8017 auto nt = new CoreWorkerThread(EventLoopType.HelperWorker); 8018 helperRunners ~= nt; 8019 nt.start(); 8020 } 8021 8022 const expectedCount = numberOfHelpers + numberOfTaskRunners; 8023 8024 while(startedCount < expectedCount) { 8025 Thread.yield(); 8026 } 8027 8028 started = true; 8029 } 8030 } 8031 8032 void cancelAll() { 8033 foreach(runner; taskRunners) 8034 runner.cancel(); 8035 foreach(runner; helperRunners) 8036 runner.cancel(); 8037 8038 } 8039 } 8040 } 8041 8042 private int numberOfCpus() { 8043 return 4; // FIXME 8044 } 8045 8046 /++ 8047 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. 8048 8049 Normally, this means writing a main like this: 8050 8051 --- 8052 import arsd.core; 8053 void main() { 8054 ArsdCoreApplication app = ArsdCoreApplication("Your app name"); 8055 8056 // do your setup here 8057 8058 // the rest of your code here 8059 } 8060 --- 8061 8062 Its destructor runs the event loop then waits to for the workers to finish to clean them up. 8063 +/ 8064 // FIXME: single instance? 8065 version(HasThread) struct ArsdCoreApplication { 8066 private ICoreEventLoop impl; 8067 8068 /++ 8069 default number of threads is to split your cpus between blocking function runners and task runners 8070 +/ 8071 this(string applicationName) { 8072 auto num = numberOfCpus(); 8073 num /= 2; 8074 if(num <= 0) 8075 num = 1; 8076 this(applicationName, num, num); 8077 } 8078 8079 /++ 8080 8081 +/ 8082 this(string applicationName, int numberOfTaskRunners, int numberOfHelpers) { 8083 impl = getThisThreadEventLoop(EventLoopType.Explicit); 8084 CoreWorkerThread.setup(numberOfTaskRunners, numberOfHelpers); 8085 } 8086 8087 @disable this(); 8088 @disable this(this); 8089 /++ 8090 This must be deterministically destroyed. 8091 +/ 8092 @disable new(); 8093 8094 ~this() { 8095 if(!alreadyRun) 8096 run(); 8097 exitApplication(); 8098 waitForWorkersToExit(3000); 8099 } 8100 8101 void exitApplication() { 8102 CoreWorkerThread.cancelAll(); 8103 } 8104 8105 void waitForWorkersToExit(int timeoutMilliseconds) { 8106 8107 } 8108 8109 private bool alreadyRun; 8110 8111 void run() { 8112 impl.run(() => false); 8113 alreadyRun = true; 8114 } 8115 } 8116 8117 8118 private class CoreEventLoopImplementation : ICoreEventLoop { 8119 version(EmptyEventLoop) RunOnceResult runOnce(Duration timeout = Duration.max) { return RunOnceResult(RunOnceResult.Possibilities.LocalExit); } 8120 version(EmptyCoreEvent) 8121 { 8122 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb){return typeof(return).init;} 8123 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb){return typeof(return).init;} 8124 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb){return typeof(return).init;} 8125 private void rearmFd(RearmToken token) {} 8126 } 8127 8128 8129 private { 8130 static struct LoopIterationDelegate { 8131 void delegate() dg; 8132 uint flags; 8133 } 8134 LoopIterationDelegate[] loopIterationDelegates; 8135 8136 void runLoopIterationDelegates(bool isAfter) { 8137 foreach(lid; loopIterationDelegates) 8138 if((!isAfter && (lid.flags & 1)) || (isAfter && (lid.flags & 2))) 8139 lid.dg(); 8140 } 8141 } 8142 8143 UnregisterToken addDelegateOnLoopIteration(void delegate() dg, uint timingFlags) { 8144 loopIterationDelegates ~= LoopIterationDelegate(dg, timingFlags); 8145 UnregisterToken ut; 8146 ut.impl = this; 8147 ut.dg = dg; 8148 return ut; 8149 } 8150 8151 void unregisterDg(void delegate() dg) { 8152 LoopIterationDelegate[] toKeep; 8153 foreach(lid; loopIterationDelegates) { 8154 if(lid.dg !is dg) { 8155 toKeep ~= lid; 8156 } 8157 } 8158 loopIterationDelegates = toKeep; 8159 } 8160 8161 version(Arsd_core_dispatch) { 8162 8163 private NSRunLoop ttrl; 8164 8165 private this() { 8166 ttrl = NSRunLoop.currentRunLoop; 8167 } 8168 8169 // FIXME: this lies!! it runs until completion 8170 RunOnceResult runOnce(Duration timeout = Duration.max) { 8171 scope(exit) eventLoopRound++; 8172 8173 // FIXME: autorelease pool 8174 8175 if(false /*isWorker*/) { 8176 runLoopIterationDelegates(false); 8177 8178 // FIXME: timeout is wrong 8179 auto retValue = ttrl.runMode(NSDefaultRunLoopMode, /+beforeDate:+/ NSDate.distantFuture); 8180 if(retValue == false) 8181 throw new Exception("could not start run loop"); 8182 8183 runLoopIterationDelegates(true); 8184 8185 // NSApp.run(); 8186 // exitApplication(); 8187 //return RunOnceResult(RunOnceResult.Possibilities.GlobalExit); 8188 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 8189 } else { 8190 // ui thread needs to pump nsapp events... 8191 runLoopIterationDelegates(false); 8192 8193 auto timeoutNs = NSDate.distantFuture; // FIXME timeout here, future means no timeout 8194 8195 again: 8196 NSEvent event = NSApp.nextEventMatchingMask( 8197 NSEventMask.NSEventMaskAny, 8198 timeoutNs, 8199 NSDefaultRunLoopMode, 8200 true 8201 ); 8202 if(event !is null) { 8203 NSApp.sendEvent(event); 8204 timeoutNs = NSDate.distantPast; // only keep going if it won't block; we just want to clear the queue 8205 goto again; 8206 } 8207 8208 runLoopIterationDelegates(true); 8209 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 8210 } 8211 } 8212 8213 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 8214 auto input_src = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatch_get_main_queue()); 8215 // FIXME: can the GC reap this prematurely? 8216 auto b = block(() { 8217 cb.call(); 8218 }); 8219 // FIXME: should prolly free it eventually idk 8220 import core.memory; 8221 GC.addRoot(b); 8222 8223 dispatch_source_set_event_handler(input_src, b); 8224 // dispatch_source_set_cancel_handler(input_src, ^{ close(my_file); }); 8225 dispatch_resume(input_src); 8226 8227 return UnregisterToken(this, fd, cb); 8228 8229 } 8230 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 8231 throw new NotYetImplementedException(); 8232 } 8233 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 8234 throw new NotYetImplementedException(); 8235 } 8236 private void rearmFd(RearmToken token) { 8237 if(token.readable) 8238 cast(void) addCallbackOnFdReadableOneShot(token.fd, token.cb); 8239 else 8240 cast(void) addCallbackOnFdWritableOneShot(token.fd, token.cb); 8241 } 8242 } 8243 8244 version(Arsd_core_kqueue) { 8245 // this thread apc dispatches go as a custom event to the queue 8246 // 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. 8247 8248 RunOnceResult runOnce(Duration timeout = Duration.max) { 8249 scope(exit) eventLoopRound++; 8250 8251 runLoopIterationDelegates(false); 8252 8253 kevent_t[16] ev; 8254 //timespec tout = timespec(1, 0); 8255 auto nev = kevent(kqueuefd, null, 0, ev.ptr, ev.length, null/*&tout*/); 8256 if(nev == -1) { 8257 // FIXME: EINTR 8258 throw new SystemApiException("kevent", errno); 8259 } else if(nev == 0) { 8260 // timeout 8261 } else { 8262 foreach(event; ev[0 .. nev]) { 8263 if(event.filter == EVFILT_SIGNAL) { 8264 // FIXME: I could prolly do this better tbh 8265 markSignalOccurred(cast(int) event.ident); 8266 signalChecker(); 8267 } else { 8268 // FIXME: event.filter more specific? 8269 CallbackHelper cb = cast(CallbackHelper) event.udata; 8270 cb.call(); 8271 } 8272 } 8273 } 8274 8275 runLoopIterationDelegates(true); 8276 8277 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 8278 } 8279 8280 // FIXME: idk how to make one event that multiple kqueues can listen to w/o being shared 8281 // maybe a shared kqueue could work that the thread kqueue listen to (which i rejected for 8282 // epoll cuz it caused thundering herd problems but maybe it'd work here) 8283 8284 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 8285 kevent_t ev; 8286 8287 EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 8288 8289 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 8290 8291 return UnregisterToken(this, fd, cb); 8292 } 8293 8294 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 8295 kevent_t ev; 8296 8297 EV_SET(&ev, fd, EVFILT_READ, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 8298 8299 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 8300 8301 return RearmToken(true, this, fd, cb, 0); 8302 } 8303 8304 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 8305 kevent_t ev; 8306 8307 EV_SET(&ev, fd, EVFILT_WRITE, EV_ADD | EV_ENABLE/* | EV_ONESHOT*/, 0, 0, cast(void*) cb); 8308 8309 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 8310 8311 return RearmToken(false, this, fd, cb, 0); 8312 } 8313 8314 private void rearmFd(RearmToken token) { 8315 if(token.readable) 8316 cast(void) addCallbackOnFdReadableOneShot(token.fd, token.cb); 8317 else 8318 cast(void) addCallbackOnFdWritableOneShot(token.fd, token.cb); 8319 } 8320 8321 private void triggerGlobalEvent() { 8322 ubyte a; 8323 import core.sys.posix.unistd; 8324 write(kqueueGlobalFd[1], &a, 1); 8325 } 8326 8327 private this() { 8328 kqueuefd = ErrnoEnforce!kqueue(); 8329 setCloExec(kqueuefd); // FIXME O_CLOEXEC 8330 8331 if(kqueueGlobalFd[0] == 0) { 8332 import core.sys.posix.unistd; 8333 pipe(kqueueGlobalFd); 8334 setCloExec(kqueueGlobalFd[0]); 8335 setCloExec(kqueueGlobalFd[1]); 8336 8337 signal(SIGINT, SIG_IGN); // FIXME 8338 } 8339 8340 kevent_t ev; 8341 8342 EV_SET(&ev, SIGCHLD, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, null); 8343 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 8344 EV_SET(&ev, SIGINT, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, null); 8345 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 8346 8347 globalEventSent = new CallbackHelper(&readGlobalEvent); 8348 EV_SET(&ev, kqueueGlobalFd[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, cast(void*) globalEventSent); 8349 ErrnoEnforce!kevent(kqueuefd, &ev, 1, null, 0, null); 8350 } 8351 8352 private int kqueuefd = -1; 8353 8354 private CallbackHelper globalEventSent; 8355 void readGlobalEvent() { 8356 kevent_t event; 8357 8358 import core.sys.posix.unistd; 8359 ubyte a; 8360 read(kqueueGlobalFd[0], &a, 1); 8361 8362 // FIXME: the thread is woken up, now we need to check the circualr buffer queue 8363 } 8364 8365 private __gshared int[2] kqueueGlobalFd; 8366 } 8367 8368 /+ 8369 // this setup needs no extra allocation 8370 auto op = read(file, buffer); 8371 op.oncomplete = &thisfiber.call; 8372 op.start(); 8373 thisfiber.yield(); 8374 auto result = op.waitForCompletion(); // guaranteed to return instantly thanks to previous setup 8375 8376 can generically abstract that into: 8377 8378 auto result = thisTask.await(read(file, buffer)); 8379 8380 8381 You MUST NOT use buffer in any way - not read, modify, deallocate, reuse, anything - until the PendingOperation is complete. 8382 8383 Note that PendingOperation may just be a wrapper around an internally allocated object reference... but then if you do a waitForFirstToComplete what happens? 8384 8385 those could of course just take the value type things 8386 +/ 8387 8388 8389 version(Arsd_core_windows) { 8390 // all event loops share the one iocp, Windows 8391 // manages how to do it 8392 __gshared HANDLE iocpTaskRunners; 8393 __gshared HANDLE iocpWorkers; 8394 8395 HANDLE[] handles; 8396 CallbackHelper[] handlesCbs; 8397 8398 void unregisterHandle(HANDLE handle, CallbackHelper cb) { 8399 foreach(idx, h; handles) 8400 if(h is handle && handlesCbs[idx] is cb) { 8401 handles[idx] = handles[$-1]; 8402 handles = handles[0 .. $-1].assumeSafeAppend; 8403 8404 handlesCbs[idx] = handlesCbs[$-1]; 8405 handlesCbs = handlesCbs[0 .. $-1].assumeSafeAppend; 8406 } 8407 } 8408 8409 UnregisterToken addCallbackOnHandleReady(HANDLE handle, CallbackHelper cb) { 8410 handles ~= handle; 8411 handlesCbs ~= cb; 8412 8413 return UnregisterToken(this, handle, cb); 8414 } 8415 8416 // 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. 8417 8418 bool isWorker; // if it is a worker we wait on the iocp, if not we wait on msg 8419 8420 RunOnceResult runOnce(Duration timeout = Duration.max) { 8421 scope(exit) eventLoopRound++; 8422 8423 runLoopIterationDelegates(false); 8424 8425 if(isWorker) { 8426 // this function is only supported on Windows Vista and up, so using this 8427 // means dropping support for XP. 8428 //GetQueuedCompletionStatusEx(); 8429 assert(0); // FIXME 8430 } else { 8431 auto wto = getTimeout(); 8432 8433 auto waitResult = MsgWaitForMultipleObjectsEx( 8434 cast(int) handles.length, handles.ptr, 8435 (wto == 0 ? INFINITE : wto), /* timeout */ 8436 0x04FF, /* QS_ALLINPUT */ 8437 0x0002 /* MWMO_ALERTABLE */ | 0x0004 /* MWMO_INPUTAVAILABLE */); 8438 8439 enum WAIT_OBJECT_0 = 0; 8440 if(waitResult >= WAIT_OBJECT_0 && waitResult < handles.length + WAIT_OBJECT_0) { 8441 auto h = handles[waitResult - WAIT_OBJECT_0]; 8442 auto cb = handlesCbs[waitResult - WAIT_OBJECT_0]; 8443 cb.call(); 8444 } else if(waitResult == handles.length + WAIT_OBJECT_0) { 8445 // message ready 8446 int count; 8447 MSG message; 8448 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 8449 auto ret = GetMessage(&message, null, 0, 0); 8450 if(ret == -1) 8451 throw new WindowsApiException("GetMessage", GetLastError()); 8452 TranslateMessage(&message); 8453 DispatchMessage(&message); 8454 8455 count++; 8456 if(count > 10) 8457 break; // take the opportunity to catch up on other events 8458 8459 if(ret == 0) { // WM_QUIT 8460 exitApplication(); 8461 } 8462 } 8463 } else if(waitResult == 0x000000C0L /* WAIT_IO_COMPLETION */) { 8464 SleepEx(0, true); // I call this to give it a chance to do stuff like async io 8465 } else if(waitResult == 258L /* WAIT_TIMEOUT */) { 8466 // timeout, should never happen since we aren't using it 8467 } else if(waitResult == 0xFFFFFFFF) { 8468 // failed 8469 throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()); 8470 } else { 8471 // idk.... 8472 } 8473 } 8474 8475 runLoopIterationDelegates(true); 8476 8477 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 8478 } 8479 } 8480 8481 version(Posix) { 8482 private __gshared uint sigChildHappened = 0; 8483 private __gshared uint sigIntrHappened = 0; 8484 8485 static void signalChecker() { 8486 if(cas(&sigChildHappened, 1, 0)) { 8487 while(true) { // multiple children could have exited before we processed the notification 8488 8489 // Means child stopped, terminated, or continued. Not necessarily just terminated! 8490 8491 import core.sys.posix.sys.wait; 8492 8493 int status; 8494 auto pid = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED); 8495 if(pid == -1) { 8496 import core.stdc.errno; 8497 auto errno = errno; 8498 if(errno == ECHILD) 8499 break; // also all done, there are no children left 8500 // no need to check EINTR since we set WNOHANG 8501 throw new ErrnoApiException("waitpid", errno); 8502 } 8503 if(pid == 0) 8504 break; // all done, all children are still running 8505 8506 // look up the pid for one of our objects 8507 // if it is found, inform it of its status 8508 // and then inform its controlling thread 8509 // to wake up so it can check its waitForCompletion, 8510 // trigger its callbacks, etc. 8511 8512 ExternalProcess.recordChildChanged(pid, status); 8513 } 8514 8515 } 8516 if(cas(&sigIntrHappened, 1, 0)) { 8517 // FIXME 8518 import core.stdc.stdlib; 8519 exit(0); 8520 } 8521 } 8522 8523 /++ 8524 Informs the arsd.core system that the given signal happened. You can call this from inside a signal handler. 8525 +/ 8526 public static void markSignalOccurred(int sigNumber) nothrow { 8527 import core.sys.posix.unistd; 8528 8529 if(sigNumber == SIGCHLD) 8530 volatileStore(&sigChildHappened, 1); 8531 if(sigNumber == SIGINT) 8532 volatileStore(&sigIntrHappened, 1); 8533 8534 version(Arsd_core_epoll) { 8535 ulong writeValue = 1; 8536 write(signalPipeFd, &writeValue, writeValue.sizeof); 8537 } 8538 } 8539 } 8540 8541 version(Arsd_core_epoll) { 8542 8543 import core.sys.linux.epoll; 8544 import core.sys.linux.sys.eventfd; 8545 8546 private this() { 8547 8548 if(!globalsInitialized) { 8549 synchronized { 8550 if(!globalsInitialized) { 8551 // blocking signals is problematic because it is inherited by child processes 8552 // and that can be problematic for general purpose stuff so i use a self pipe 8553 // here. though since it is linux, im using an eventfd instead just to notify 8554 signalPipeFd = ErrnoEnforce!eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); 8555 signalReaderCallback = new CallbackHelper(&signalReader); 8556 8557 runInTaskRunnerQueue = new CallbackQueue("task runners", true); 8558 runInHelperThreadQueue = new CallbackQueue("helper threads", true); 8559 8560 setSignalHandlers(); 8561 8562 globalsInitialized = true; 8563 } 8564 } 8565 } 8566 8567 epollfd = epoll_create1(EPOLL_CLOEXEC); 8568 8569 // FIXME: ensure UI events get top priority 8570 8571 // global listeners 8572 8573 // FIXME: i should prolly keep the tokens and release them when tearing down. 8574 8575 cast(void) addCallbackOnFdReadable(signalPipeFd, signalReaderCallback); 8576 if(true) { // FIXME: if this is a task runner vs helper thread vs ui thread 8577 cast(void) addCallbackOnFdReadable(runInTaskRunnerQueue.fd, runInTaskRunnerQueue.callback); 8578 runInTaskRunnerQueue.callback.addref(); 8579 } else { 8580 cast(void) addCallbackOnFdReadable(runInHelperThreadQueue.fd, runInHelperThreadQueue.callback); 8581 runInHelperThreadQueue.callback.addref(); 8582 } 8583 8584 // local listener 8585 thisThreadQueue = new CallbackQueue("this thread", false); 8586 cast(void) addCallbackOnFdReadable(thisThreadQueue.fd, thisThreadQueue.callback); 8587 8588 // what are we going to do about timers? 8589 } 8590 8591 void teardown() { 8592 import core.sys.posix.fcntl; 8593 import core.sys.posix.unistd; 8594 8595 close(epollfd); 8596 epollfd = -1; 8597 8598 thisThreadQueue.teardown(); 8599 8600 // FIXME: should prolly free anything left in the callback queue, tho those could also be GC managed tbh. 8601 } 8602 8603 /+ // i call it explicitly at the thread exit instead, but worker threads aren't really supposed to exit generally speaking till process done anyway 8604 static ~this() { 8605 teardown(); 8606 } 8607 +/ 8608 8609 static void teardownGlobals() { 8610 import core.sys.posix.fcntl; 8611 import core.sys.posix.unistd; 8612 8613 synchronized { 8614 restoreSignalHandlers(); 8615 close(signalPipeFd); 8616 signalReaderCallback.release(); 8617 8618 runInTaskRunnerQueue.teardown(); 8619 runInHelperThreadQueue.teardown(); 8620 8621 globalsInitialized = false; 8622 } 8623 8624 } 8625 8626 8627 private static final class CallbackQueue : SynchronizableObject { 8628 int fd = -1; 8629 string name; 8630 CallbackHelper callback; 8631 SynchronizedCircularBuffer!CallbackHelper queue; 8632 8633 this(string name, bool dequeueIsShared) { 8634 this.name = name; 8635 queue = typeof(queue)(this); 8636 8637 fd = ErrnoEnforce!eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | (dequeueIsShared ? EFD_SEMAPHORE : 0)); 8638 8639 callback = new CallbackHelper(dequeueIsShared ? &sharedDequeueCb : &threadLocalDequeueCb); 8640 } 8641 8642 bool resetEvent() { 8643 import core.sys.posix.unistd; 8644 ulong count; 8645 return read(fd, &count, count.sizeof) == count.sizeof; 8646 } 8647 8648 void sharedDequeueCb() { 8649 if(resetEvent()) { 8650 auto cb = queue.dequeue(); 8651 cb.call(); 8652 cb.release(); 8653 } 8654 } 8655 8656 void threadLocalDequeueCb() { 8657 CallbackHelper[16] buffer; 8658 foreach(cb; queue.dequeueSeveral(buffer[], () { resetEvent(); })) { 8659 cb.call(); 8660 cb.release(); 8661 } 8662 } 8663 8664 void enqueue(CallbackHelper cb) { 8665 if(queue.enqueue(cb)) { 8666 import core.sys.posix.unistd; 8667 ulong count = 1; 8668 ErrnoEnforce!write(fd, &count, count.sizeof); 8669 } else { 8670 throw new ArsdException!"queue is full"(name); 8671 } 8672 } 8673 8674 void teardown() { 8675 import core.sys.posix.fcntl; 8676 import core.sys.posix.unistd; 8677 8678 close(fd); 8679 fd = -1; 8680 8681 callback.release(); 8682 } 8683 } 8684 8685 // there's a global instance of this we refer back to 8686 private __gshared { 8687 bool globalsInitialized; 8688 8689 CallbackHelper signalReaderCallback; 8690 8691 CallbackQueue runInTaskRunnerQueue; 8692 CallbackQueue runInHelperThreadQueue; 8693 8694 int exitEventFd = -1; // FIXME: implement 8695 } 8696 8697 // and then the local loop 8698 private { 8699 int epollfd = -1; 8700 8701 CallbackQueue thisThreadQueue; 8702 } 8703 8704 // signal stuff { 8705 import core.sys.posix.signal; 8706 8707 private __gshared sigaction_t oldSigIntr; 8708 private __gshared sigaction_t oldSigChld; 8709 private __gshared sigaction_t oldSigPipe; 8710 8711 private __gshared int signalPipeFd = -1; 8712 // sigpipe not important, i handle errors on the writes 8713 8714 public static void setSignalHandlers() { 8715 static extern(C) void interruptHandler(int sigNumber) nothrow { 8716 markSignalOccurred(sigNumber); 8717 8718 /+ 8719 // calling the old handler is non-trivial since there can be ignore 8720 // or default or a plain handler or a sigaction 3 arg handler and i 8721 // i don't think it is worth teh complication 8722 sigaction_t* oldHandler; 8723 if(sigNumber == SIGCHLD) 8724 oldHandler = &oldSigChld; 8725 else if(sigNumber == SIGINT) 8726 oldHandler = &oldSigIntr; 8727 if(oldHandler && oldHandler.sa_handler) 8728 oldHandler 8729 +/ 8730 } 8731 8732 sigaction_t n; 8733 n.sa_handler = &interruptHandler; 8734 n.sa_mask = cast(sigset_t) 0; 8735 n.sa_flags = 0; 8736 sigaction(SIGINT, &n, &oldSigIntr); 8737 sigaction(SIGCHLD, &n, &oldSigChld); 8738 8739 n.sa_handler = SIG_IGN; 8740 sigaction(SIGPIPE, &n, &oldSigPipe); 8741 } 8742 8743 public static void restoreSignalHandlers() { 8744 sigaction(SIGINT, &oldSigIntr, null); 8745 sigaction(SIGCHLD, &oldSigChld, null); 8746 sigaction(SIGPIPE, &oldSigPipe, null); 8747 } 8748 8749 private static void signalReader() { 8750 import core.sys.posix.unistd; 8751 ulong number; 8752 read(signalPipeFd, &number, number.sizeof); 8753 8754 signalChecker(); 8755 } 8756 // signal stuff done } 8757 8758 // the any thread poll is just registered in the this thread poll w/ exclusive. nobody actaully epoll_waits 8759 // on the global one directly. 8760 8761 RunOnceResult runOnce(Duration timeout = Duration.max) { 8762 scope(exit) eventLoopRound++; 8763 8764 runLoopIterationDelegates(false); 8765 8766 epoll_event[16] events; 8767 auto ret = epoll_wait(epollfd, events.ptr, cast(int) events.length, getTimeout ? getTimeout() : -1); // FIXME: timeout argument 8768 if(ret == -1) { 8769 import core.stdc.errno; 8770 if(errno == EINTR) { 8771 return RunOnceResult(RunOnceResult.Possibilities.Interrupted); 8772 } 8773 throw new ErrnoApiException("epoll_wait", errno); 8774 } else if(ret == 0) { 8775 // timeout 8776 } else { 8777 // loop events and call associated callbacks 8778 foreach(event; events[0 .. ret]) { 8779 auto flags = event.events; 8780 auto cbObject = cast(CallbackHelper) event.data.ptr; 8781 8782 // FIXME: or if it is an error... 8783 // 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) 8784 8785 cbObject.call(); 8786 } 8787 } 8788 8789 runLoopIterationDelegates(true); 8790 8791 return RunOnceResult(RunOnceResult.Possibilities.CarryOn); 8792 } 8793 8794 // building blocks for low-level integration with the loop 8795 8796 UnregisterToken addCallbackOnFdReadable(int fd, CallbackHelper cb) { 8797 epoll_event event; 8798 event.data.ptr = cast(void*) cb; 8799 event.events = EPOLLIN | EPOLLEXCLUSIVE; 8800 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 8801 throw new ErrnoApiException("epoll_ctl", errno); 8802 8803 return UnregisterToken(this, fd, cb); 8804 } 8805 8806 /++ 8807 Adds a one-off callback that you can optionally rearm when it happens. 8808 +/ 8809 RearmToken addCallbackOnFdReadableOneShot(int fd, CallbackHelper cb) { 8810 epoll_event event; 8811 event.data.ptr = cast(void*) cb; 8812 event.events = EPOLLIN | EPOLLONESHOT; 8813 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 8814 throw new ErrnoApiException("epoll_ctl", errno); 8815 8816 return RearmToken(true, this, fd, cb, EPOLLIN | EPOLLONESHOT); 8817 } 8818 8819 /++ 8820 Adds a one-off callback that you can optionally rearm when it happens. 8821 +/ 8822 RearmToken addCallbackOnFdWritableOneShot(int fd, CallbackHelper cb) { 8823 epoll_event event; 8824 event.data.ptr = cast(void*) cb; 8825 event.events = EPOLLOUT | EPOLLONESHOT; 8826 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event) == -1) 8827 throw new ErrnoApiException("epoll_ctl", errno); 8828 8829 return RearmToken(false, this, fd, cb, EPOLLOUT | EPOLLONESHOT); 8830 } 8831 8832 private void unregisterFd(int fd) { 8833 epoll_event event; 8834 if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &event) == -1) 8835 throw new ErrnoApiException("epoll_ctl", errno); 8836 } 8837 8838 private void rearmFd(RearmToken token) { 8839 epoll_event event; 8840 event.data.ptr = cast(void*) token.cb; 8841 event.events = token.flags; 8842 if(epoll_ctl(epollfd, EPOLL_CTL_MOD, token.fd, &event) == -1) 8843 throw new ErrnoApiException("epoll_ctl", errno); 8844 } 8845 8846 // Disk files will have to be sent as messages to a worker to do the read and report back a completion packet. 8847 } 8848 8849 version(Arsd_core_kqueue) { 8850 // FIXME 8851 } 8852 8853 // cross platform adapters 8854 void setTimeout() {} 8855 void addFileOrDirectoryChangeListener(FilePath name, uint flags, bool recursive = false) {} 8856 } 8857 8858 // deduplication???????// 8859 bool postMessage(ThreadToRunIn destination, void delegate() code) { 8860 return false; 8861 } 8862 bool postMessage(ThreadToRunIn destination, Object message) { 8863 return false; 8864 } 8865 8866 /+ 8867 void main() { 8868 // FIXME: the offset doesn't seem to be done right 8869 auto file = new AsyncFile(FilePath("test.txt"), AsyncFile.OpenMode.writeWithTruncation); 8870 file.write("hello", 10).waitForCompletion(); 8871 } 8872 +/ 8873 8874 // to test the mailboxes 8875 /+ 8876 void main() { 8877 /+ 8878 import std.stdio; 8879 Thread[4] pool; 8880 8881 bool shouldExit; 8882 8883 static int received; 8884 8885 static void tester() { 8886 received++; 8887 //writeln(cast(void*) Thread.getThis, " ", received); 8888 } 8889 8890 foreach(ref thread; pool) { 8891 thread = new Thread(() { 8892 getThisThreadEventLoop().run(() { 8893 return shouldExit; 8894 }); 8895 }); 8896 thread.start(); 8897 } 8898 8899 getThisThreadEventLoop(); // ensure it is all initialized before proceeding. FIXME: i should have an ensure initialized function i do on most the public apis. 8900 8901 int lol; 8902 8903 try 8904 foreach(i; 0 .. 6000) { 8905 CoreEventLoopImplementation.runInTaskRunnerQueue.enqueue(new CallbackHelper(&tester)); 8906 lol = cast(int) i; 8907 } 8908 catch(ArsdExceptionBase e) { 8909 Thread.sleep(50.msecs); 8910 writeln(e); 8911 writeln(lol); 8912 } 8913 8914 import core.stdc.stdlib; 8915 exit(0); 8916 8917 version(none) 8918 foreach(i; 0 .. 100) 8919 CoreEventLoopImplementation.runInTaskRunnerQueue.enqueue(new CallbackHelper(&tester)); 8920 8921 8922 foreach(ref thread; pool) { 8923 thread.join(); 8924 } 8925 +/ 8926 8927 8928 static int received; 8929 8930 static void tester() { 8931 received++; 8932 //writeln(cast(void*) Thread.getThis, " ", received); 8933 } 8934 8935 8936 8937 auto ev = cast(CoreEventLoopImplementation) getThisThreadEventLoop(); 8938 foreach(i; 0 .. 100) 8939 ev.thisThreadQueue.enqueue(new CallbackHelper(&tester)); 8940 foreach(i; 0 .. 100 / 16 + 1) 8941 ev.runOnce(); 8942 import std.conv; 8943 assert(received == 100, to!string(received)); 8944 8945 } 8946 +/ 8947 8948 /++ 8949 This is primarily a helper for the event queues. It is public in the hope it might be useful, 8950 but subject to change without notice; I will treat breaking it the same as if it is private. 8951 (That said, it is a simple little utility that does its job, so it is unlikely to change much. 8952 The biggest change would probably be letting it grow and changing from inline to dynamic array.) 8953 8954 It is a fixed-size ring buffer that synchronizes on a given object you give it in the constructor. 8955 8956 After enqueuing something, you should probably set an event to notify the other threads. This is left 8957 as an exercise to you (or another wrapper). 8958 +/ 8959 struct SynchronizedCircularBuffer(T, size_t maxSize = 128) { 8960 private T[maxSize] ring; 8961 private int front; 8962 private int back; 8963 8964 private SynchronizableObject synchronizedOn; 8965 8966 @disable this(); 8967 8968 /++ 8969 The Object's monitor is used to synchronize the methods in here. 8970 +/ 8971 this(SynchronizableObject synchronizedOn) { 8972 this.synchronizedOn = synchronizedOn; 8973 } 8974 8975 /++ 8976 Note the potential race condition between calling this and actually dequeuing something. You might 8977 want to acquire the lock on the object before calling this (nested synchronized things are allowed 8978 as long as the same thread is the one doing it). 8979 +/ 8980 bool isEmpty() { 8981 synchronized(this.synchronizedOn) { 8982 return front == back; 8983 } 8984 } 8985 8986 /++ 8987 Note the potential race condition between calling this and actually queuing something. 8988 +/ 8989 bool isFull() { 8990 synchronized(this.synchronizedOn) { 8991 return isFullUnsynchronized(); 8992 } 8993 } 8994 8995 private bool isFullUnsynchronized() nothrow const { 8996 return ((back + 1) % ring.length) == front; 8997 8998 } 8999 9000 /++ 9001 If this returns true, you should signal listening threads (with an event or a semaphore, 9002 depending on how you dequeue it). If it returns false, the queue was full and your thing 9003 was NOT added. You might wait and retry later (you could set up another event to signal it 9004 has been read and wait for that, or maybe try on a timer), or just fail and throw an exception 9005 or to abandon the message. 9006 +/ 9007 bool enqueue(T what) { 9008 synchronized(this.synchronizedOn) { 9009 if(isFullUnsynchronized()) 9010 return false; 9011 ring[(back++) % ring.length] = what; 9012 return true; 9013 } 9014 } 9015 9016 private T dequeueUnsynchronized() nothrow { 9017 assert(front != back); 9018 return ring[(front++) % ring.length]; 9019 } 9020 9021 /++ 9022 If you are using a semaphore to signal, you can call this once for each count of it 9023 and you can do that separately from this call (though they should be paired). 9024 9025 If you are using an event, you should use [dequeueSeveral] instead to drain it. 9026 +/ 9027 T dequeue() { 9028 synchronized(this.synchronizedOn) { 9029 return dequeueUnsynchronized(); 9030 } 9031 } 9032 9033 /++ 9034 Note that if you use a semaphore to signal waiting threads, you should probably not call this. 9035 9036 If you use a set/reset event, there's a potential race condition between the dequeue and event 9037 reset. This is why the `runInsideLockIfEmpty` delegate is there - when it is empty, before it 9038 unlocks, it will give you a chance to reset the event. Otherwise, it can remain set to indicate 9039 that there's still pending data in the queue. 9040 +/ 9041 T[] dequeueSeveral(return T[] buffer, scope void delegate() runInsideLockIfEmpty = null) { 9042 int pos; 9043 synchronized(this.synchronizedOn) { 9044 while(pos < buffer.length && front != back) { 9045 buffer[pos++] = dequeueUnsynchronized(); 9046 } 9047 if(front == back && runInsideLockIfEmpty !is null) 9048 runInsideLockIfEmpty(); 9049 } 9050 return buffer[0 .. pos]; 9051 } 9052 } 9053 9054 unittest { 9055 SynchronizableObject object = new SynchronizableObject(); 9056 auto queue = SynchronizedCircularBuffer!CallbackHelper(object); 9057 assert(queue.isEmpty); 9058 foreach(i; 0 .. queue.ring.length - 1) 9059 queue.enqueue(cast(CallbackHelper) cast(void*) i); 9060 assert(queue.isFull); 9061 9062 foreach(i; 0 .. queue.ring.length - 1) 9063 assert(queue.dequeue() is (cast(CallbackHelper) cast(void*) i)); 9064 assert(queue.isEmpty); 9065 9066 foreach(i; 0 .. queue.ring.length - 1) 9067 queue.enqueue(cast(CallbackHelper) cast(void*) i); 9068 assert(queue.isFull); 9069 9070 CallbackHelper[] buffer = new CallbackHelper[](300); 9071 auto got = queue.dequeueSeveral(buffer); 9072 assert(got.length == queue.ring.length - 1); 9073 assert(queue.isEmpty); 9074 foreach(i, item; got) 9075 assert(item is (cast(CallbackHelper) cast(void*) i)); 9076 9077 foreach(i; 0 .. 8) 9078 queue.enqueue(cast(CallbackHelper) cast(void*) i); 9079 buffer = new CallbackHelper[](4); 9080 got = queue.dequeueSeveral(buffer); 9081 assert(got.length == 4); 9082 foreach(i, item; got) 9083 assert(item is (cast(CallbackHelper) cast(void*) i)); 9084 got = queue.dequeueSeveral(buffer); 9085 assert(got.length == 4); 9086 foreach(i, item; got) 9087 assert(item is (cast(CallbackHelper) cast(void*) (i+4))); 9088 got = queue.dequeueSeveral(buffer); 9089 assert(got.length == 0); 9090 assert(queue.isEmpty); 9091 } 9092 9093 /++ 9094 9095 +/ 9096 enum ByteOrder { 9097 irrelevant, 9098 littleEndian, 9099 bigEndian, 9100 } 9101 9102 /++ 9103 A class to help write a stream of binary data to some target. 9104 9105 NOT YET FUNCTIONAL 9106 +/ 9107 class WritableStream { 9108 /++ 9109 9110 +/ 9111 this(size_t bufferSize) { 9112 this(new ubyte[](bufferSize)); 9113 } 9114 9115 /// ditto 9116 this(ubyte[] buffer) { 9117 this.buffer = buffer; 9118 } 9119 9120 /++ 9121 9122 +/ 9123 final void put(T)(T value, ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 9124 static if(T.sizeof == 8) 9125 ulong b; 9126 else static if(T.sizeof == 4) 9127 uint b; 9128 else static if(T.sizeof == 2) 9129 ushort b; 9130 else static if(T.sizeof == 1) 9131 ubyte b; 9132 else static assert(0, "unimplemented type, try using just the basic types"); 9133 9134 if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1) 9135 throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte", "WritableStream.put", file, line); 9136 9137 final switch(byteOrder) { 9138 case ByteOrder.irrelevant: 9139 writeOneByte(b); 9140 break; 9141 case ByteOrder.littleEndian: 9142 foreach(i; 0 .. T.sizeof) { 9143 writeOneByte(b & 0xff); 9144 b >>= 8; 9145 } 9146 break; 9147 case ByteOrder.bigEndian: 9148 int amount = T.sizeof * 8 - 8; 9149 foreach(i; 0 .. T.sizeof) { 9150 writeOneByte((b >> amount) & 0xff); 9151 amount -= 8; 9152 } 9153 break; 9154 } 9155 } 9156 9157 /// ditto 9158 final void put(T : E[], E)(T value, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 9159 foreach(item; value) 9160 put(item, elementByteOrder, file, line); 9161 } 9162 9163 /++ 9164 Performs a final flush() call, then marks the stream as closed, meaning no further data will be written to it. 9165 +/ 9166 void close() { 9167 isClosed_ = true; 9168 } 9169 9170 /++ 9171 Writes what is currently in the buffer to the target and waits for the target to accept it. 9172 Please note: if you are subclassing this to go to a different target 9173 +/ 9174 void flush() {} 9175 9176 /++ 9177 Returns true if either you closed it or if the receiving end closed their side, indicating they 9178 don't want any more data. 9179 +/ 9180 bool isClosed() { 9181 return isClosed_; 9182 } 9183 9184 // hasRoomInBuffer 9185 // canFlush 9186 // waitUntilCanFlush 9187 9188 // flushImpl 9189 // markFinished / close - tells the other end you're done 9190 9191 private final writeOneByte(ubyte value) { 9192 if(bufferPosition == buffer.length) 9193 flush(); 9194 9195 buffer[bufferPosition++] = value; 9196 } 9197 9198 9199 private { 9200 ubyte[] buffer; 9201 int bufferPosition; 9202 bool isClosed_; 9203 } 9204 } 9205 9206 /++ 9207 A stream can be used by just one task at a time, but one task can consume multiple streams. 9208 9209 Streams may be populated by async sources (in which case they must be called from a fiber task), 9210 from a function generating the data on demand (including an input range), from memory, or from a synchronous file. 9211 9212 A stream of heterogeneous types is compatible with input ranges. 9213 9214 It reads binary data. 9215 +/ 9216 version(HasThread) class ReadableStream { 9217 9218 this() { 9219 9220 } 9221 9222 /++ 9223 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). 9224 9225 --- 9226 // get an int out of a big endian stream 9227 int i = stream.get!int(ByteOrder.bigEndian); 9228 9229 // get i bytes off the stream 9230 ubyte[] data = stream.get!(ubyte[])(i); 9231 --- 9232 +/ 9233 final T get(T)(ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 9234 if(byteOrder == ByteOrder.irrelevant && T.sizeof > 1) 9235 throw new InvalidArgumentsException("byteOrder", "byte order must be specified for type " ~ T.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 9236 9237 // FIXME: what if it is a struct? 9238 9239 while(bufferedLength() < T.sizeof) 9240 waitForAdditionalData(); 9241 9242 static if(T.sizeof == 1) { 9243 ubyte ret = consumeOneByte(); 9244 return *cast(T*) &ret; 9245 } else { 9246 static if(T.sizeof == 8) 9247 ulong ret; 9248 else static if(T.sizeof == 4) 9249 uint ret; 9250 else static if(T.sizeof == 2) 9251 ushort ret; 9252 else static assert(0, "unimplemented type, try using just the basic types"); 9253 9254 if(byteOrder == ByteOrder.littleEndian) { 9255 typeof(ret) buffer; 9256 foreach(b; 0 .. T.sizeof) { 9257 buffer = consumeOneByte(); 9258 buffer <<= T.sizeof * 8 - 8; 9259 9260 ret >>= 8; 9261 ret |= buffer; 9262 } 9263 } else { 9264 foreach(b; 0 .. T.sizeof) { 9265 ret <<= 8; 9266 ret |= consumeOneByte(); 9267 } 9268 } 9269 9270 return *cast(T*) &ret; 9271 } 9272 } 9273 9274 /// ditto 9275 final T get(T : E[], E)(size_t length, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 9276 if(elementByteOrder == ByteOrder.irrelevant && E.sizeof > 1) 9277 throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 9278 9279 // if the stream is closed before getting the length or the terminator, should we send partial stuff 9280 // or just throw? 9281 9282 while(bufferedLength() < length * E.sizeof) 9283 waitForAdditionalData(); 9284 9285 T ret; 9286 9287 ret.length = length; 9288 9289 if(false && elementByteOrder == ByteOrder.irrelevant) { 9290 // ret[] = 9291 // FIXME: can prolly optimize 9292 } else { 9293 foreach(i; 0 .. length) 9294 ret[i] = get!E(elementByteOrder); 9295 } 9296 9297 return ret; 9298 9299 } 9300 9301 /// ditto 9302 final T get(T : E[], E)(scope bool delegate(E e) isTerminatingSentinel, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__) { 9303 if(elementByteOrder == ByteOrder.irrelevant && E.sizeof > 1) 9304 throw new InvalidArgumentsException("elementByteOrder", "byte order must be specified for type " ~ E.stringof ~ " because it is bigger than one byte", "ReadableStream.get", file, line); 9305 9306 T ret; 9307 9308 do { 9309 try 9310 ret ~= get!E(elementByteOrder); 9311 catch(ArsdException!"is already closed" ae) 9312 return ret; 9313 } while(!isTerminatingSentinel(ret[$-1])); 9314 9315 return ret[0 .. $-1]; // cut off the terminating sentinel 9316 } 9317 9318 /++ 9319 9320 +/ 9321 bool isClosed() { 9322 return isClosed_ && currentBuffer.length == 0 && leftoverBuffer.length == 0; 9323 } 9324 9325 // Control side of things 9326 9327 private bool isClosed_; 9328 9329 /++ 9330 Feeds data into the stream, which can be consumed by `get`. If a task is waiting for more 9331 data to satisfy its get requests, this will trigger those tasks to resume. 9332 9333 If you feed it empty data, it will mark the stream as closed. 9334 +/ 9335 void feedData(ubyte[] data) { 9336 if(data.length == 0) 9337 isClosed_ = true; 9338 9339 currentBuffer = data; 9340 // this is a borrowed buffer, so we won't keep the reference long term 9341 scope(exit) 9342 currentBuffer = null; 9343 9344 if(waitingTask !is null) { 9345 waitingTask.call(); 9346 } 9347 } 9348 9349 /++ 9350 You basically have to use this thing from a task 9351 +/ 9352 protected void waitForAdditionalData() { 9353 if(isClosed_) 9354 throw ArsdException!("is already closed")(); 9355 9356 Fiber task = Fiber.getThis; 9357 9358 assert(task !is null); 9359 9360 if(waitingTask !is null && waitingTask !is task) 9361 throw new ArsdException!"streams can only have one waiting task"; 9362 9363 // copy any pending data in our buffer to the longer-term buffer 9364 if(currentBuffer.length) 9365 leftoverBuffer ~= currentBuffer; 9366 9367 waitingTask = task; 9368 task.yield(); 9369 } 9370 9371 private Fiber waitingTask; 9372 private ubyte[] leftoverBuffer; 9373 private ubyte[] currentBuffer; 9374 9375 private size_t bufferedLength() { 9376 return leftoverBuffer.length + currentBuffer.length; 9377 } 9378 9379 private ubyte consumeOneByte() { 9380 ubyte b; 9381 if(leftoverBuffer.length) { 9382 b = leftoverBuffer[0]; 9383 leftoverBuffer = leftoverBuffer[1 .. $]; 9384 } else if(currentBuffer.length) { 9385 b = currentBuffer[0]; 9386 currentBuffer = currentBuffer[1 .. $]; 9387 } else { 9388 assert(0, "consuming off an empty buffer is impossible"); 9389 } 9390 9391 return b; 9392 } 9393 } 9394 9395 // FIXME: do a stringstream too 9396 9397 unittest { 9398 auto stream = new ReadableStream(); 9399 9400 int position; 9401 char[16] errorBuffer; 9402 9403 auto fiber = new Fiber(() { 9404 position = 1; 9405 int a = stream.get!int(ByteOrder.littleEndian); 9406 assert(a == 10, intToString(a, errorBuffer[])); 9407 position = 2; 9408 ubyte b = stream.get!ubyte; 9409 assert(b == 33); 9410 position = 3; 9411 9412 // ubyte[] c = stream.get!(ubyte[])(3); 9413 // int[] d = stream.get!(int[])(3); 9414 }); 9415 9416 fiber.call(); 9417 assert(position == 1); 9418 stream.feedData([10, 0, 0, 0]); 9419 assert(position == 2); 9420 stream.feedData([33]); 9421 assert(position == 3); 9422 9423 // stream.feedData([1,2,3]); 9424 // stream.feedData([1,2,3,4,1,2,3,4,1,2,3,4]); 9425 } 9426 9427 private char[] asciiToUpper(scope const(char)[] s) pure { 9428 char[] copy = s.dup; 9429 foreach(ref ch; copy) { 9430 if(ch >= 'a' && ch <= 'z') 9431 ch -= 32; 9432 } 9433 return copy; 9434 } 9435 9436 /++ 9437 UNSTABLE, NOT FULLY IMPLEMENTED. DO NOT USE YET. 9438 9439 You might use this like: 9440 9441 --- 9442 auto proc = new ExternalProcess(); 9443 auto stdoutStream = new ReadableStream(); 9444 9445 // to use a stream you can make one and have a task consume it 9446 runTask({ 9447 while(!stdoutStream.isClosed) { 9448 auto line = stdoutStream.get!string(e => e == '\n'); 9449 } 9450 }); 9451 9452 // then make the process feed into the stream 9453 proc.onStdoutAvailable = (got) { 9454 stdoutStream.feedData(got); // send it to the stream for processing 9455 stdout.rawWrite(got); // forward it through to our own thing 9456 // could also append it to a buffer to return it on complete 9457 }; 9458 proc.start(); 9459 --- 9460 9461 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. 9462 +/ 9463 class ExternalProcess /*: AsyncOperationRequest*/ { 9464 9465 private static version(Posix) { 9466 __gshared ExternalProcess[pid_t] activeChildren; 9467 9468 void recordChildCreated(pid_t pid, ExternalProcess proc) { 9469 synchronized(typeid(ExternalProcess)) { 9470 activeChildren[pid] = proc; 9471 } 9472 } 9473 9474 void recordChildChanged(pid_t pid, int status) { 9475 synchronized(typeid(ExternalProcess)) { 9476 if(pid in activeChildren) { 9477 auto ac = activeChildren[pid]; 9478 9479 // import unix = core.sys.posix.unistd; unix.write(1, "SIGCHLD\n".ptr, 8); 9480 9481 import core.sys.posix.sys.wait; 9482 if(WIFEXITED(status)) { 9483 // exited normally 9484 ac.markComplete(WEXITSTATUS(status)); 9485 activeChildren.remove(pid); 9486 } else if(WIFSIGNALED(status)) { 9487 // terminated by signal 9488 9489 // version(linux) import core.sys.linux.sys.wait : WCOREDUMP; 9490 9491 bool coredumped; 9492 static if(is(typeof(WCOREDUMP))) { 9493 if(WCOREDUMP(status)) { 9494 coredumped = true; 9495 } 9496 } 9497 9498 ac.markTerminatedBySignal(WTERMSIG(status), coredumped); 9499 activeChildren.remove(pid); 9500 } else if(WIFSTOPPED(status)) { 9501 // stopped by signal 9502 ac.markStoppedBySignal(WSTOPSIG(status)); 9503 } else if(WIFCONTINUED(status)) { 9504 // continued by SIGCONT 9505 ac.markContinued(); 9506 } else { 9507 // unknown condition...... 9508 } 9509 } 9510 } 9511 } 9512 } 9513 9514 // FIXME: config to pass through a shell or not 9515 9516 /++ 9517 This is the native version for Windows. 9518 +/ 9519 version(Windows) 9520 this(FilePath program, string commandLine) { 9521 version(Posix) { 9522 assert(0, "not implemented command line to posix args yet"); 9523 } else version(Windows) { 9524 this.program = program; 9525 this.commandLine = commandLine; 9526 } 9527 else throw new NotYetImplementedException(); 9528 } 9529 9530 /+ 9531 this(string commandLine) { 9532 version(Posix) { 9533 assert(0, "not implemented command line to posix args yet"); 9534 } 9535 else throw new NotYetImplementedException(); 9536 } 9537 9538 this(string[] args) { 9539 version(Posix) { 9540 this.program = FilePath(args[0]); 9541 this.args = args; 9542 } 9543 else throw new NotYetImplementedException(); 9544 } 9545 +/ 9546 9547 /++ 9548 This is the native version for Posix. 9549 +/ 9550 version(Posix) 9551 this(FilePath program, string[] args) { 9552 version(Posix) { 9553 this.program = program; 9554 this.args = args; 9555 } 9556 else throw new NotYetImplementedException(); 9557 } 9558 9559 /++ 9560 This allows you to record a process as existing to the core event loop, 9561 so you get completion and other notifications on it, but without doing any 9562 other processing. The process should already exist as a child of our main process 9563 and you should not attempt to use any of the i/o files on it, as they will be null. 9564 9565 History: 9566 Added December 18, 2025 9567 +/ 9568 version(Posix) 9569 this(pid_t recordForMinimalWrapping) { 9570 recordChildCreated(recordForMinimalWrapping, this); 9571 } 9572 9573 version(Posix) 9574 package { 9575 // if you use the override thing, it is YOUR responsibility to close them! 9576 int overrideStdin = -2; 9577 int overrideStdout = -2; 9578 int overrideStderr = -2; 9579 int pgid = -2; 9580 9581 const(char*)* environment; 9582 9583 // FIXME: change it to string[string] 9584 // it will modify the passed AA 9585 void setEnvironmentWithModifications(string[string] mods) @system { 9586 const(char*)[] ret; 9587 9588 const(char*)* head = environ; 9589 while(*head) { 9590 auto headz = stringz(*head); 9591 // see if head and any of the mods are the same var 9592 auto headd = headz.borrow; 9593 auto equal = headd.indexOf("="); 9594 if(equal == -1) 9595 equal = cast(int) headd.length; 9596 auto name = headd[0 .. equal]; 9597 if(name in mods) { 9598 ret ~= cast(char*) (name ~ "=" ~ mods[name] ~ "\0").ptr; 9599 mods.remove(cast(string) name); 9600 } else { 9601 ret ~= *head; 9602 } 9603 9604 head++; 9605 } 9606 9607 // append the remainder of mods to the ret 9608 foreach(name, value; mods) 9609 ret ~= cast(char*) (name ~ "=" ~ value ~ "\0").ptr; 9610 9611 ret ~= null; 9612 9613 environment = ret.ptr; 9614 } 9615 } 9616 9617 version(Windows) 9618 package { 9619 // if you use the override thing, it is YOUR responsibility to close them! 9620 HANDLE overrideStdin = INVALID_HANDLE_VALUE; 9621 HANDLE overrideStdout = INVALID_HANDLE_VALUE; 9622 HANDLE overrideStderr = INVALID_HANDLE_VALUE; 9623 9624 wchar* environment; 9625 9626 void setEnvironmentWithModifications(string[string] mods) @system { 9627 wchar[] ret; 9628 9629 // FIXME: case sensitivity in name lookup,the passed mods should all be uppercase 9630 9631 // FIXME: "All strings in the environment block must be sorted alphabetically by name. The sort is case-insensitive, Unicode order, without regard to locale." 9632 9633 auto originalEnv = GetEnvironmentStringsW(); 9634 if(originalEnv is null) 9635 throw new WindowsApiException("GetEnvironmentStringsW",GetLastError()); 9636 scope(exit) { 9637 if(originalEnv) 9638 FreeEnvironmentStringsW(originalEnv); 9639 } 9640 9641 // read null terminated strings until we hit one of zero length 9642 // create a new block of memory with the same data, but all copied 9643 auto env = originalEnv; 9644 more: 9645 wchar* start = env; 9646 while(*env) { 9647 env++; 9648 } 9649 wchar[] wv = start[0 .. env - start]; 9650 if(wv.length) { 9651 string v = makeUtf8StringFromWindowsString(wv); 9652 auto equal = v.indexOf("="); 9653 if(equal == -1) 9654 equal = cast(int) v.length; 9655 auto name = v[0 .. equal].asciiToUpper; 9656 9657 if(name in mods) { 9658 WCharzBuffer bfr = (name ~ "=" ~ mods[name]); 9659 ret ~= bfr.ptr[0 .. bfr.length + 1]; // to include the zero terminator 9660 mods.remove(cast(string) name); 9661 } else { 9662 ret ~= start[0 .. env - start + 1]; // include zero terminator 9663 } 9664 9665 env++; // move past the zero terminator 9666 goto more; 9667 } 9668 9669 foreach(name, mod; mods) { 9670 WCharzBuffer bfr = (name ~ "=" ~ mod); 9671 ret ~= bfr.ptr[0 .. bfr.length + 1]; // to include the zero terminator 9672 } 9673 9674 ret ~= 0; 9675 9676 this.environment = ret.ptr; 9677 } 9678 } 9679 9680 /++ 9681 9682 +/ 9683 void start() { 9684 version(Posix) { 9685 getThisThreadEventLoop(); // ensure it is initialized 9686 9687 int ret; 9688 9689 int[2] stdinPipes; 9690 if(overrideStdin == -2) { 9691 ret = pipe(stdinPipes); 9692 if(ret == -1) 9693 throw new ErrnoApiException("stdin pipe", errno); 9694 } 9695 9696 scope(failure) { 9697 if(overrideStdin == -2) { 9698 close(stdinPipes[0]); 9699 close(stdinPipes[1]); 9700 } 9701 } 9702 9703 auto stdinFd = overrideStdin == -2 ? stdinPipes[1] : -1; 9704 9705 int[2] stdoutPipes; 9706 if(overrideStdout == -2) { 9707 ret = pipe(stdoutPipes); 9708 if(ret == -1) 9709 throw new ErrnoApiException("stdout pipe", errno); 9710 } 9711 9712 scope(failure) { 9713 if(overrideStdout == -2) { 9714 close(stdoutPipes[0]); 9715 close(stdoutPipes[1]); 9716 } 9717 } 9718 9719 auto stdoutFd = overrideStdout == -2 ? stdoutPipes[0] : -1; 9720 9721 int[2] stderrPipes; 9722 if(overrideStderr == -2) { 9723 ret = pipe(stderrPipes); 9724 if(ret == -1) 9725 throw new ErrnoApiException("stderr pipe", errno); 9726 } 9727 9728 scope(failure) { 9729 if(overrideStderr == -2) { 9730 close(stderrPipes[0]); 9731 close(stderrPipes[1]); 9732 } 9733 } 9734 9735 auto stderrFd = overrideStderr == -2 ? stderrPipes[0] : -1; 9736 9737 9738 int[2] errorReportPipes; 9739 ret = pipe(errorReportPipes); 9740 if(ret == -1) 9741 throw new ErrnoApiException("error reporting pipe", errno); 9742 9743 scope(failure) { 9744 close(errorReportPipes[0]); 9745 close(errorReportPipes[1]); 9746 } 9747 9748 setCloExec(errorReportPipes[0]); 9749 setCloExec(errorReportPipes[1]); 9750 9751 // writeln(pgid); 9752 auto forkRet = fork(); 9753 if(forkRet == -1) 9754 throw new ErrnoApiException("fork", errno); 9755 if(forkRet == 0) { 9756 // child side 9757 9758 // FIXME can we do more error checking that is actually useful here? 9759 // these operations are virtually guaranteed to succeed given the setup anyway. 9760 9761 // FIXME pty too 9762 9763 void fail(int step) { 9764 import core.stdc.errno; 9765 auto code = errno; 9766 9767 // report the info back to the parent then exit 9768 9769 int[2] msg = [step, code]; 9770 auto ret = write(errorReportPipes[1], msg.ptr, msg.sizeof); 9771 9772 // but if this fails there's not much we can do... 9773 9774 import core.stdc.stdlib; 9775 exit(1); 9776 } 9777 9778 // both parent and child are supposed to try to set it 9779 if(pgid != -2) { 9780 setpgid(0, pgid == 0 ? getpid() : pgid); 9781 } 9782 9783 // dup2 closes the fd it is replacing automatically 9784 // then don't need either of the original pipe fds anymore 9785 if(overrideStdin == -2) { 9786 dup2(stdinPipes[0], 0); 9787 close(stdinPipes[0]); 9788 close(stdinPipes[1]); 9789 } else if(overrideStdin != 0) { 9790 dup2(overrideStdin, 0); 9791 close(overrideStdin); 9792 } 9793 9794 if(overrideStdout == -2) { 9795 dup2(stdoutPipes[1], 1); 9796 close(stdoutPipes[0]); 9797 close(stdoutPipes[1]); 9798 } else if(overrideStdout != 1) { 9799 dup2(overrideStdout, 1); 9800 close(overrideStdout); 9801 } 9802 9803 if(overrideStderr == -2) { 9804 dup2(stderrPipes[1], 2); 9805 close(stderrPipes[0]); 9806 close(stderrPipes[1]); 9807 } else if(overrideStderr != 2) { 9808 dup2(overrideStderr, 2); 9809 close(overrideStderr); 9810 } 9811 9812 // the error reporting pipe will be closed upon exec since we set cloexec before fork 9813 // and everything else should have cloexec set too hopefully. 9814 9815 if(beforeExec) 9816 beforeExec(); 9817 9818 // i'm not sure that a fully-initialized druntime is still usable 9819 // after a fork(), so i'm gonna stick to the C lib in here. 9820 9821 const(char)* file = mallocedStringz(program.path).ptr; 9822 if(file is null) 9823 fail(1); 9824 const(char)*[] argv = mallocSlice!(const(char)*)(args.length + 1); 9825 if(argv is null) 9826 fail(2); 9827 foreach(idx, arg; args) { 9828 argv[idx] = mallocedStringz(args[idx]).ptr; 9829 if(argv[idx] is null) 9830 fail(3); 9831 } 9832 argv[args.length] = null; 9833 9834 auto rete = execve(file, argv.ptr, this.environment is null ? environ : this.environment); // FIXME: i used to use execvp, which searches path but i think i like this more 9835 if(rete == -1) { 9836 fail(4); 9837 } else { 9838 // unreachable code, exec never returns if it succeeds 9839 assert(0); 9840 } 9841 } else { 9842 pid = forkRet; 9843 9844 // both parent and child are supposed to try to set it 9845 if(pgid != -2) { 9846 setpgid(pid, pgid == 0 ? pid : pgid); 9847 } 9848 9849 recordChildCreated(pid, this); 9850 9851 // close our copy of the write side of the error reporting pipe 9852 // so the read will immediately give eof when the fork closes it too 9853 ErrnoEnforce!close(errorReportPipes[1]); 9854 9855 int[2] msg; 9856 // this will block to wait for it to actually either start up or fail to exec (which should be near instant) 9857 try_again: 9858 auto val = read(errorReportPipes[0], msg.ptr, msg.sizeof); 9859 9860 if(val == -1) { 9861 if(errno == EINTR) 9862 goto try_again; 9863 throw new ErrnoApiException("read error report", errno); 9864 } 9865 9866 if(val == msg.sizeof) { 9867 // error happened 9868 // FIXME: keep the step part of the error report too 9869 throw new ErrnoApiException("exec", msg[1]); 9870 } else if(val == 0) { 9871 // pipe closed, meaning exec succeeded 9872 } else { 9873 assert(0); // never supposed to happen 9874 } 9875 9876 // set the ones we keep to close upon future execs 9877 // and close the others 9878 if(overrideStdin == -2) { 9879 setCloExec(stdinPipes[1]); 9880 ErrnoEnforce!close(stdinPipes[0]); 9881 makeNonBlocking(stdinFd); 9882 _stdin = new AsyncFile(stdinFd); 9883 } 9884 9885 if(overrideStdout == -2) { 9886 setCloExec(stdoutPipes[0]); 9887 ErrnoEnforce!close(stdoutPipes[1]); 9888 makeNonBlocking(stdoutFd); 9889 _stdout = new AsyncFile(stdoutFd); 9890 } 9891 9892 if(overrideStderr == -2) { 9893 setCloExec(stderrPipes[0]); 9894 ErrnoEnforce!close(stderrPipes[1]); 9895 makeNonBlocking(stderrFd); 9896 _stderr = new AsyncFile(stderrFd); 9897 } 9898 9899 ErrnoEnforce!close(errorReportPipes[0]); 9900 } 9901 } else version(Windows) { 9902 WCharzBuffer program = this.program.path; 9903 WCharzBuffer cmdLine = this.commandLine; 9904 9905 PROCESS_INFORMATION pi; 9906 STARTUPINFOW startupInfo; 9907 9908 SECURITY_ATTRIBUTES saAttr; 9909 saAttr.nLength = SECURITY_ATTRIBUTES.sizeof; 9910 saAttr.bInheritHandle = true; 9911 saAttr.lpSecurityDescriptor = null; 9912 9913 HANDLE inreadPipe; 9914 HANDLE inwritePipe; 9915 9916 if(overrideStdin == INVALID_HANDLE_VALUE) { 9917 if(MyCreatePipeEx(&inreadPipe, &inwritePipe, &saAttr, 0, 0, FILE_FLAG_OVERLAPPED) == 0) 9918 throw new WindowsApiException("CreatePipe", GetLastError()); 9919 if(!SetHandleInformation(inwritePipe, 1/*HANDLE_FLAG_INHERIT*/, 0)) 9920 throw new WindowsApiException("SetHandleInformation", GetLastError()); 9921 } 9922 9923 scope(failure) { 9924 if(overrideStdin == INVALID_HANDLE_VALUE) { 9925 CloseHandle(inreadPipe); 9926 CloseHandle(inwritePipe); 9927 } 9928 } 9929 9930 HANDLE outreadPipe; 9931 HANDLE outwritePipe; 9932 if(overrideStdout == INVALID_HANDLE_VALUE) { 9933 if(MyCreatePipeEx(&outreadPipe, &outwritePipe, &saAttr, 0, FILE_FLAG_OVERLAPPED, 0) == 0) 9934 throw new WindowsApiException("CreatePipe", GetLastError()); 9935 if(!SetHandleInformation(outreadPipe, 1/*HANDLE_FLAG_INHERIT*/, 0)) 9936 throw new WindowsApiException("SetHandleInformation", GetLastError()); 9937 } 9938 9939 scope(failure) { 9940 if(overrideStdout == INVALID_HANDLE_VALUE) { 9941 CloseHandle(outreadPipe); 9942 CloseHandle(outwritePipe); 9943 } 9944 } 9945 9946 9947 HANDLE errreadPipe; 9948 HANDLE errwritePipe; 9949 if(overrideStderr == INVALID_HANDLE_VALUE) { 9950 if(MyCreatePipeEx(&errreadPipe, &errwritePipe, &saAttr, 0, FILE_FLAG_OVERLAPPED, 0) == 0) 9951 throw new WindowsApiException("CreatePipe", GetLastError()); 9952 if(!SetHandleInformation(errreadPipe, 1/*HANDLE_FLAG_INHERIT*/, 0)) 9953 throw new WindowsApiException("SetHandleInformation", GetLastError()); 9954 } 9955 9956 scope(failure) { 9957 if(overrideStderr == INVALID_HANDLE_VALUE) { 9958 CloseHandle(errreadPipe); 9959 CloseHandle(errwritePipe); 9960 } 9961 } 9962 9963 9964 startupInfo.cb = startupInfo.sizeof; 9965 startupInfo.dwFlags = STARTF_USESTDHANDLES; 9966 startupInfo.hStdInput = (overrideStdin == INVALID_HANDLE_VALUE) ? inreadPipe : overrideStdin; 9967 startupInfo.hStdOutput = (overrideStdout == INVALID_HANDLE_VALUE) ? outwritePipe : overrideStdout; 9968 startupInfo.hStdError = (overrideStderr == INVALID_HANDLE_VALUE) ? errwritePipe : overrideStderr; 9969 9970 auto result = CreateProcessW( 9971 program.ptr, 9972 cmdLine.ptr, 9973 null, // process attributes 9974 null, // thread attributes 9975 true, // inherit handles; necessary for the std in/out/err ones to work 9976 this.environment is null ? 0 : CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags FIXME might be useful to change 9977 this.environment, // environment, might be worth changing 9978 null, // current directory 9979 &startupInfo, 9980 &pi 9981 ); 9982 9983 if(!result) 9984 throw new WindowsApiException("CreateProcessW", GetLastError()); 9985 9986 if(overrideStdin == INVALID_HANDLE_VALUE) { 9987 _stdin = new AsyncFile(inwritePipe); 9988 Win32Enforce!CloseHandle(inreadPipe); 9989 } 9990 if(overrideStdout == INVALID_HANDLE_VALUE) { 9991 _stdout = new AsyncFile(outreadPipe); 9992 Win32Enforce!CloseHandle(outwritePipe); 9993 } 9994 if(overrideStderr == INVALID_HANDLE_VALUE) { 9995 _stderr = new AsyncFile(errreadPipe); 9996 Win32Enforce!CloseHandle(errwritePipe); 9997 } 9998 9999 Win32Enforce!CloseHandle(pi.hThread); 10000 10001 handle = pi.hProcess; 10002 10003 procRegistration = getThisThreadEventLoop.addCallbackOnHandleReady(handle, new CallbackHelper(&almostComplete)); 10004 } 10005 } 10006 10007 version(Windows) { 10008 private HANDLE handle; 10009 private FilePath program; 10010 private string commandLine; 10011 private ICoreEventLoop.UnregisterToken procRegistration; 10012 10013 private final void almostComplete() { 10014 // GetProcessTimes lol 10015 Win32Enforce!GetExitCodeProcess(handle, cast(uint*) &_status); 10016 10017 markComplete(_status); 10018 10019 procRegistration.unregister(); 10020 CloseHandle(handle); 10021 this.completed = true; 10022 } 10023 } else version(Posix) { 10024 import core.sys.posix.unistd; 10025 import core.sys.posix.fcntl; 10026 10027 package pid_t pid = -1; 10028 10029 public void delegate() beforeExec; 10030 10031 private FilePath program; 10032 private string[] args; 10033 } 10034 10035 private final void markComplete(int status) { 10036 completed = true; 10037 _status = status; 10038 10039 if(oncomplete) 10040 oncomplete(this); 10041 } 10042 private final void markTerminatedBySignal(int signal, bool coredumped) { 10043 completed = true; 10044 _status = -signal; 10045 this.coredumped = coredumped; 10046 10047 if(oncomplete) 10048 oncomplete(this); 10049 } 10050 private final void markStoppedBySignal(int signal) { 10051 stopped = true; 10052 _status = -signal; 10053 } 10054 private final void markContinued() { 10055 stopped = false; 10056 _status = int.min; 10057 } 10058 10059 private AsyncFile _stdin; 10060 private AsyncFile _stdout; 10061 private AsyncFile _stderr; 10062 10063 /++ 10064 10065 +/ 10066 AsyncFile stdin() { 10067 return _stdin; 10068 } 10069 /// ditto 10070 AsyncFile stdout() { 10071 return _stdout; 10072 } 10073 /// ditto 10074 AsyncFile stderr() { 10075 return _stderr; 10076 } 10077 10078 /++ 10079 +/ 10080 void waitForCompletion() { 10081 getThisThreadEventLoop().run(&this.isComplete); 10082 } 10083 10084 /++ 10085 History: 10086 Added November 23, 2025 10087 +/ 10088 void waitForChange() { 10089 bool stoppedAtFirst = isStopped; 10090 getThisThreadEventLoop().run(() { return this.isComplete || (stoppedAtFirst != this.isStopped); }); 10091 } 10092 10093 /++ 10094 +/ 10095 bool isComplete() { 10096 return completed; 10097 } 10098 /++ 10099 History: 10100 Added November 23, 2025 10101 +/ 10102 bool isStopped() { 10103 return stopped; 10104 } 10105 /++ 10106 History: 10107 Added November 23, 2025 10108 +/ 10109 bool leftCoreDump() { 10110 return coredumped; 10111 } 10112 10113 private bool coredumped; 10114 private bool stopped; 10115 private bool completed; 10116 private int _status = int.min; 10117 10118 /++ 10119 +/ 10120 int status() { 10121 return _status; 10122 } 10123 10124 // void delegate(int code) onTermination; 10125 10126 void delegate(ExternalProcess) oncomplete; 10127 10128 // pty? 10129 } 10130 10131 // FIXME: comment this out 10132 /+ 10133 unittest { 10134 auto proc = new ExternalProcess(FilePath("/bin/cat"), ["/bin/cat"]); 10135 10136 getThisThreadEventLoop(); // initialize it 10137 10138 int c = 0; 10139 proc.onStdoutAvailable = delegate(ubyte[] got) { 10140 if(c == 0) 10141 assert(cast(string) got == "hello!"); 10142 else 10143 assert(got.length == 0); 10144 // import std.stdio; writeln(got); 10145 c++; 10146 }; 10147 10148 proc.start(); 10149 10150 assert(proc.pid != -1); 10151 10152 10153 import std.stdio; 10154 Thread[4] pool; 10155 10156 bool shouldExit; 10157 10158 static int received; 10159 10160 proc.writeToStdin("hello!"); 10161 proc.writeToStdin(null); // closes the pipe 10162 10163 proc.waitForCompletion(); 10164 10165 assert(proc.status == 0); 10166 10167 assert(c == 2); 10168 10169 // writeln("here"); 10170 } 10171 +/ 10172 10173 // to test the thundering herd on signal handling 10174 version(none) 10175 unittest { 10176 Thread[4] pool; 10177 foreach(ref thread; pool) { 10178 thread = new class Thread { 10179 this() { 10180 super({ 10181 int count; 10182 getThisThreadEventLoop().run(() { 10183 if(count > 4) return true; 10184 count++; 10185 return false; 10186 }); 10187 }); 10188 } 10189 }; 10190 thread.start(); 10191 } 10192 foreach(ref thread; pool) { 10193 thread.join(); 10194 } 10195 } 10196 10197 /+ 10198 ================ 10199 LOGGER FRAMEWORK 10200 ================ 10201 +/ 10202 /++ 10203 DO NOT USE THIS YET IT IS NOT FUNCTIONAL NOR STABLE 10204 10205 10206 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 strings written to some file. 10207 10208 A library (or an application) defines a log source. They write to this source. 10209 10210 Applications then define log listeners, zero or more, which reads from various sources and does something with them. 10211 10212 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. 10213 10214 10215 An application can also set a default subscriber which applies to all log objects throughout. 10216 10217 All log message objects must be capable of being converted to strings and to json. 10218 10219 Ad-hoc messages can be done with interpolated sequences. 10220 10221 Messages automatically get a timestamp. They can also have file/line and maybe even a call stack. 10222 10223 Examples: 10224 --- 10225 auto logger = new shared LoggerOf!GenericEmbeddableInterpolatedSequence; 10226 10227 mylogger.info(i"$this heartbeat"); 10228 --- 10229 10230 History: 10231 Added May 27, 2024 10232 10233 Not actually implemented until February 6, 2025, when it changed from mixin template to class. 10234 +/ 10235 class LoggerOf(T, size_t bufferSize = 16) : SynchronizableObject { 10236 private LoggedMessage!T[bufferSize] ring; 10237 private ulong writeBufferPosition; 10238 10239 import core.sync.mutex; 10240 import core.sync.condition; 10241 10242 private Mutex mutex; 10243 private Condition condition; 10244 private bool active; 10245 private int listenerCount; 10246 10247 this() shared { 10248 mutex = new shared Mutex(cast(LoggerOf) this); 10249 condition = new shared Condition(mutex); 10250 active = true; 10251 } 10252 10253 /++ 10254 Closes the log channel and waits for all listeners to finish pending work before returning. 10255 10256 Once the logger is closed, it cannot be used again. 10257 10258 You should close any logger you attached listeners to before returning from `main()`. 10259 +/ 10260 void close() shared { 10261 synchronized(this) { 10262 active = false; 10263 condition.notifyAll(); 10264 10265 while(listenerCount > 0) { 10266 condition.wait(); 10267 } 10268 } 10269 } 10270 10271 /++ 10272 10273 Examples: 10274 10275 --- 10276 // to write all messages to the console 10277 logger.addListener((message, missedMessageCount) { 10278 writeln(message); 10279 }); 10280 --- 10281 10282 --- 10283 // to only write warnings and errors 10284 logger.addListener((message, missedMessageCount) { 10285 if(message.level >= LogLevel.warn) 10286 writeln(message); 10287 }); 10288 --- 10289 10290 --- 10291 // to ignore messages from arsd.core 10292 logger.addListener((message, missedMessageCount) { 10293 if(message.sourceLocation.moduleName != "arsd.core") 10294 writeln(message); 10295 }); 10296 --- 10297 +/ 10298 LogListenerController addListener(void delegate(LoggedMessage!T message, int missedMessages) dg) shared { 10299 static class Listener : Thread, LogListenerController { 10300 shared LoggerOf logger; 10301 ulong readBufferPosition; 10302 void delegate(LoggedMessage!T, int) dg; 10303 10304 bool connected; 10305 10306 import core.sync.event; 10307 Event event; 10308 10309 this(shared LoggerOf logger, void delegate(LoggedMessage!T msg, int) dg) { 10310 this.dg = dg; 10311 this.logger = logger; 10312 this.connected = true; 10313 this.isDaemon = true; 10314 10315 auto us = cast(LoggerOf) logger; 10316 synchronized(logger) 10317 us.listenerCount++; 10318 10319 event.initialize(true, false); 10320 super(&run); 10321 } 10322 10323 void disconnect() { 10324 this.connected = false; 10325 } 10326 10327 void run() { 10328 auto us = cast(LoggerOf) logger; 10329 /+ 10330 // can't do this due to https://github.com/ldc-developers/ldc/issues/4837 10331 // so doing the try/catch below and putting this under it 10332 scope(exit) { 10333 synchronized(logger) { 10334 us.listenerCount--; 10335 logger.condition.notifyAll(); 10336 } 10337 // mark us as complete for other listeners waiting as well 10338 static if (__traits(hasMember, event, "setIfInitialized")) { 10339 // Upstream compatibility, see <https://github.com/dlang/dmd/pull/15800>. 10340 event.setIfInitialized(); 10341 } else { 10342 // Old D runtime compatibility 10343 event.set(); 10344 } 10345 } 10346 +/ 10347 10348 try { 10349 10350 LoggedMessage!T[bufferSize] buffer; 10351 do { 10352 int missedMessages = 0; 10353 long n; 10354 synchronized(logger) { 10355 while(logger.active && connected && logger.writeBufferPosition <= readBufferPosition) { 10356 logger.condition.wait(); 10357 } 10358 10359 n = us.writeBufferPosition - readBufferPosition; 10360 if(n > bufferSize) { 10361 // we missed something... 10362 missedMessages = cast(int) (n - bufferSize); 10363 readBufferPosition = us.writeBufferPosition - bufferSize; 10364 n = bufferSize; 10365 } 10366 auto startPos = readBufferPosition % bufferSize; 10367 auto endPos = us.writeBufferPosition % bufferSize; 10368 if(endPos > startPos) { 10369 buffer[0 .. cast(size_t) n] = us.ring[cast(size_t) startPos .. cast(size_t) endPos]; 10370 } else { 10371 auto ourSplit = us.ring.length - startPos; 10372 buffer[0 .. cast(size_t) ourSplit] = us.ring[cast(size_t) startPos .. $]; 10373 buffer[cast(size_t) ourSplit .. cast(size_t) (ourSplit + endPos)] = us.ring[0 .. cast(size_t) endPos]; 10374 } 10375 readBufferPosition = us.writeBufferPosition; 10376 } 10377 foreach(item; buffer[0 .. cast(size_t) n]) { 10378 if(!connected) 10379 break; 10380 dg(item, missedMessages); 10381 missedMessages = 0; 10382 } 10383 } while(logger.active && connected); 10384 10385 } catch(Throwable t) { 10386 // i guess i could try to log the exception for other listeners to pick up... 10387 10388 } 10389 10390 synchronized(logger) { 10391 us.listenerCount--; 10392 logger.condition.notifyAll(); 10393 } 10394 // mark us as complete for other listeners waiting as well 10395 static if (__traits(hasMember, event, "setIfInitialized")) { 10396 // Upstream compatibility, see <https://github.com/dlang/dmd/pull/15800>. 10397 event.setIfInitialized(); 10398 } else { 10399 // Old D runtime compatibility 10400 event.set(); 10401 } 10402 10403 } 10404 10405 void waitForCompletion() { 10406 event.wait(); 10407 } 10408 } 10409 10410 auto listener = new Listener(this, dg); 10411 listener.start(); 10412 10413 return listener; 10414 } 10415 10416 void log(LoggedMessage!T message) shared { 10417 synchronized(this) { 10418 auto unshared = cast() this; 10419 unshared.ring[writeBufferPosition % bufferSize] = message; 10420 unshared.writeBufferPosition += 1; 10421 10422 // import std.stdio; std.stdio.writeln(message); 10423 condition.notifyAll(); 10424 } 10425 } 10426 10427 /// ditto 10428 void log(LogLevel level, T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10429 import core.stdc.time; 10430 log(LoggedMessage!T(level, sourceLocation, SimplifiedUtcTimestamp.fromUnixTime(time(null)), Thread.getThis(), message)); 10431 } 10432 10433 /// ditto 10434 void info(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10435 log(LogLevel.info, message, sourceLocation); 10436 } 10437 /// ditto 10438 void trace(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10439 log(LogLevel.trace, message, sourceLocation); 10440 } 10441 /// ditto 10442 void warn(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10443 log(LogLevel.warn, message, sourceLocation); 10444 } 10445 /// ditto 10446 void error(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10447 log(LogLevel.error, message, sourceLocation); 10448 } 10449 10450 static if(is(T == GenericEmbeddableInterpolatedSequence)) { 10451 pragma(inline, true) 10452 final void info(T...)(InterpolationHeader header, T message, InterpolationFooter footer, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10453 log(LogLevel.info, GenericEmbeddableInterpolatedSequence(header, message, footer), sourceLocation); 10454 } 10455 pragma(inline, true) 10456 final void trace(T...)(InterpolationHeader header, T message, InterpolationFooter footer, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10457 log(LogLevel.trace, GenericEmbeddableInterpolatedSequence(header, message, footer), sourceLocation); 10458 } 10459 pragma(inline, true) 10460 final void warn(T...)(InterpolationHeader header, T message, InterpolationFooter footer, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10461 log(LogLevel.warn, GenericEmbeddableInterpolatedSequence(header, message, footer), sourceLocation); 10462 } 10463 pragma(inline, true) 10464 final void error(T...)(InterpolationHeader header, T message, InterpolationFooter footer, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shared { 10465 log(LogLevel.error, GenericEmbeddableInterpolatedSequence(header, message, footer), sourceLocation); 10466 } 10467 } 10468 } 10469 10470 /// ditto 10471 interface LogListenerController { 10472 /++ 10473 Disconnects from the log producer as soon as possible, possibly leaving messages 10474 behind in the log buffer. Once disconnected, the log listener will terminate 10475 asynchronously and cannot be reused. Use [waitForCompletion] to block your thread 10476 until the termination is complete. 10477 +/ 10478 void disconnect(); 10479 10480 /++ 10481 Waits for the listener to finish its pending work and terminate. You should call 10482 [disconnect] first to make it start to exit. 10483 +/ 10484 void waitForCompletion(); 10485 } 10486 10487 /// ditto 10488 struct SourceLocation { 10489 string moduleName; 10490 size_t line; 10491 } 10492 10493 /// ditto 10494 struct LoggedMessage(T) { 10495 LogLevel level; 10496 SourceLocation sourceLocation; 10497 SimplifiedUtcTimestamp timestamp; 10498 Thread originatingThread; 10499 T message; 10500 10501 // process id can be assumed by the listener, 10502 // since it is always the same; logs are sent and received by the same process. 10503 10504 string toString() { 10505 string ret; 10506 10507 ret ~= sourceLocation.moduleName; 10508 ret ~= ":"; 10509 ret ~= toStringInternal(sourceLocation.line); 10510 ret ~= " "; 10511 if(originatingThread) { 10512 char[16] buffer; 10513 ret ~= originatingThread.name.length ? originatingThread.name : intToString(cast(long) originatingThread.id, buffer, IntToStringArgs().withRadix(16)); 10514 } 10515 ret ~= "["; 10516 ret ~= toStringInternal(level); 10517 ret ~= "] "; 10518 ret ~= timestamp.toString(); 10519 ret ~= " "; 10520 ret ~= message.toString(); 10521 10522 return ret; 10523 } 10524 // callstack? 10525 } 10526 10527 /// ditto 10528 enum LogLevel { 10529 trace, 10530 info, 10531 warn, 10532 error, 10533 } 10534 10535 private shared(LoggerOf!GenericEmbeddableInterpolatedSequence) _commonLogger; 10536 shared(LoggerOf!GenericEmbeddableInterpolatedSequence) logger() { 10537 if(_commonLogger is null) { 10538 synchronized { 10539 if(_commonLogger is null) 10540 _commonLogger = new shared LoggerOf!GenericEmbeddableInterpolatedSequence; 10541 } 10542 } 10543 10544 return _commonLogger; 10545 } 10546 10547 /++ 10548 Makes note of an exception you catch and otherwise ignore. 10549 10550 History: 10551 Added April 17, 2025 10552 +/ 10553 void logSwallowedException(Exception e) { 10554 logger.error(InterpolationHeader(), e.toString(), InterpolationFooter()); 10555 } 10556 10557 /+ 10558 // using this requires a newish compiler so we just uncomment when necessary 10559 unittest { 10560 void main() { 10561 auto logger = logger;// new shared LoggerOf!GenericEmbeddableInterpolatedSequence; 10562 LogListenerController l1; 10563 l1 = logger.addListener((msg, missedCount) { 10564 if(missedCount) 10565 writeln("T1: missed ", missedCount); 10566 writeln("T1:" ~msg.toString()); 10567 //Thread.sleep(500.msecs); 10568 //l1.disconnect(); 10569 Thread.sleep(1.msecs); 10570 }); 10571 foreach(n; 0 .. 200) { 10572 logger.info(i"hello world $n"); 10573 if(n % 6 == 0) 10574 Thread.sleep(1.msecs); 10575 } 10576 10577 logger.addListener((msg, missedCount) { 10578 if(missedCount) writeln("T2 missed ", missedCount); 10579 writeln("T2:" ~msg.toString()); 10580 }); 10581 10582 Thread.sleep(500.msecs); 10583 l1.disconnect; 10584 l1.waitForCompletion; 10585 10586 logger.close(); 10587 } 10588 //main; 10589 } 10590 +/ 10591 10592 /+ 10593 ===================== 10594 TRANSLATION FRAMEWORK 10595 ===================== 10596 +/ 10597 10598 /++ 10599 Represents a translatable string. 10600 10601 10602 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). 10603 10604 You can use `null` as a tstring. You can also construct it with UFCS: `i"foo".tstring`. 10605 10606 The actual translation engine should be set on the application level. 10607 10608 It is possible to get all translatable string templates compiled into the application at runtime. 10609 10610 History: 10611 Added June 23, 2024 10612 +/ 10613 class tstring { 10614 private GenericEmbeddableInterpolatedSequence geis; 10615 10616 /++ 10617 For a case where there is no plural specialization. 10618 +/ 10619 this(Args...)(InterpolationHeader hdr, Args args, InterpolationFooter ftr) { 10620 geis = GenericEmbeddableInterpolatedSequence(hdr, args, ftr); 10621 tstringTemplateProcessor!(Args.length, Args) tp; 10622 } 10623 10624 /+ 10625 /++ 10626 When here is a plural specialization this passes the default one. 10627 +/ 10628 this(SArgs..., Pargs...)( 10629 InterpolationHeader shdr, SArgs singularArgs, InterpolationFooter sftr, 10630 InterpolationHeader phdr, PArgs pluralArgs, InterpolationFooter pftr 10631 ) 10632 { 10633 geis = GenericEmbeddableInterpolatedSequence(shdr, singularArgs, sftr); 10634 //geis = GenericEmbeddableInterpolatedSequence(phdr, pluralArgs, pftr); 10635 10636 tstringTemplateProcessor!(Args.length, Args) tp; 10637 } 10638 +/ 10639 10640 final override string toString() { 10641 if(this is null) 10642 return null; 10643 if(translationEngine !is null) 10644 return translationEngine.translate(geis); 10645 else 10646 return geis.toString(); 10647 } 10648 10649 static tstring opCall(Args...)(InterpolationHeader hdr, Args args, InterpolationFooter ftr) { 10650 return new tstring(hdr, args, ftr); 10651 } 10652 10653 /+ +++ +/ 10654 10655 private static shared(TranslationEngine) translationEngine_ = null; 10656 10657 static shared(TranslationEngine) translationEngine() { 10658 return translationEngine_; 10659 } 10660 10661 static void translationEngine(shared TranslationEngine e) { 10662 translationEngine_ = e; 10663 if(e !is null) { 10664 auto item = first; 10665 while(item) { 10666 e.handleTemplate(*item); 10667 item = item.next; 10668 } 10669 } 10670 } 10671 10672 public struct TranslatableElement { 10673 string templ; 10674 string pluralTempl; 10675 10676 TranslatableElement* next; 10677 } 10678 10679 static __gshared TranslatableElement* first; 10680 10681 // FIXME: the template should be identified to the engine somehow 10682 10683 private static enum templateStringFor(Args...) = () { 10684 int count; 10685 string templ; 10686 foreach(arg; Args) { 10687 static if(is(arg == InterpolatedLiteral!str, string str)) 10688 templ ~= str; 10689 else static if(is(arg == InterpolatedExpression!code, string code)) 10690 templ ~= "{" ~ cast(char)(++count + '0') ~ "}"; 10691 } 10692 return templ; 10693 }(); 10694 10695 // this is here to inject static ctors so we can build up a runtime list from ct data 10696 private static struct tstringTemplateProcessor(size_t pluralBegins, Args...) { 10697 static __gshared TranslatableElement e = TranslatableElement( 10698 templateStringFor!(Args[0 .. pluralBegins]), 10699 templateStringFor!(Args[pluralBegins .. $]), 10700 null /* next, filled in by the static ctor below */); 10701 10702 @standalone @system 10703 shared static this() { 10704 e.next = first; 10705 first = &e; 10706 } 10707 } 10708 } 10709 10710 /// ditto 10711 class TranslationEngine { 10712 string translate(GenericEmbeddableInterpolatedSequence geis) shared { 10713 return geis.toString(); 10714 } 10715 10716 /++ 10717 If the translation engine has been set early in the module 10718 construction process (which it should be!) 10719 +/ 10720 void handleTemplate(tstring.TranslatableElement t) shared { 10721 } 10722 } 10723 10724 private static template WillFitInGeis(Args...) { 10725 static int lengthRequired() { 10726 int place; 10727 foreach(arg; Args) { 10728 static if(is(arg == InterpolatedLiteral!str, string str)) { 10729 if(place & 1) // can't put string in the data slot 10730 place++; 10731 place++; 10732 } else static if(is(arg == InterpolationHeader) || is(arg == InterpolationFooter) || is(arg == InterpolatedExpression!code, string code)) { 10733 // no storage required 10734 } else { 10735 if((place & 1) == 0) // can't put data in the string slot 10736 place++; 10737 place++; 10738 } 10739 } 10740 10741 if(place & 1) 10742 place++; 10743 return place / 2; 10744 } 10745 10746 enum WillFitInGeis = lengthRequired() <= GenericEmbeddableInterpolatedSequence.seq.length; 10747 } 10748 10749 10750 /+ 10751 For making an array of istrings basically; it moves their CT magic to RT dynamic type. 10752 +/ 10753 struct GenericEmbeddableInterpolatedSequence { 10754 static struct Element { 10755 string str; // these are pointers to string literals every time 10756 LimitedVariant lv; 10757 } 10758 10759 Element[8] seq; 10760 10761 this(Args...)(InterpolationHeader, Args args, InterpolationFooter) { 10762 int place; 10763 bool stringUsedInPlace; 10764 bool overflowed; 10765 10766 static assert(WillFitInGeis!(Args), "Your interpolated elements will not fit in the generic buffer."); 10767 10768 foreach(arg; args) { 10769 static if(is(typeof(arg) == InterpolatedLiteral!str, string str)) { 10770 if(stringUsedInPlace) { 10771 place++; 10772 stringUsedInPlace = false; 10773 } 10774 10775 if(place == seq.length) { 10776 overflowed = true; 10777 break; 10778 } 10779 seq[place].str = str; 10780 stringUsedInPlace = true; 10781 } else static if(is(typeof(arg) == InterpolationHeader) || is(typeof(arg) == InterpolationFooter)) { 10782 static assert(0, "Cannot embed interpolated sequences"); 10783 } else static if(is(typeof(arg) == InterpolatedExpression!code, string code)) { 10784 // irrelevant 10785 } else { 10786 if(place == seq.length) { 10787 overflowed = true; 10788 break; 10789 } 10790 seq[place].lv = LimitedVariant(arg); 10791 place++; 10792 stringUsedInPlace = false; 10793 } 10794 } 10795 } 10796 10797 string toString() { 10798 string s; 10799 foreach(item; seq) { 10800 if(item.str !is null) 10801 s ~= item.str; 10802 if(!item.lv.containsNull()) 10803 s ~= item.lv.toString(); 10804 } 10805 return s; 10806 } 10807 } 10808 10809 /+ 10810 ================= 10811 STDIO REPLACEMENT 10812 ================= 10813 +/ 10814 10815 private void appendToBuffer(ref char[] buffer, ref int pos, scope const(char)[] what) { 10816 auto required = pos + what.length; 10817 if(buffer.length < required) 10818 buffer.length = required; 10819 buffer[pos .. pos + what.length] = what[]; 10820 pos += what.length; 10821 } 10822 10823 private void appendToBuffer(ref char[] buffer, ref int pos, long what) { 10824 appendToBuffer(buffer, pos, what, IntToStringArgs.init); 10825 } 10826 private void appendToBuffer(ref char[] buffer, ref int pos, long what, IntToStringArgs args) { 10827 if(buffer.length < pos + 32) 10828 buffer.length = pos + 32; 10829 auto sliced = intToString(what, buffer[pos .. $], args); 10830 pos += sliced.length; 10831 } 10832 10833 private void appendToBuffer(ref char[] buffer, ref int pos, double what) { 10834 appendToBuffer(buffer, pos, what, FloatToStringArgs.init); 10835 } 10836 private void appendToBuffer(ref char[] buffer, ref int pos, double what, FloatToStringArgs args) { 10837 if(buffer.length < pos + 42) 10838 buffer.length = pos + 42; 10839 auto sliced = floatToString(what, buffer[pos .. $], args); 10840 pos += sliced.length; 10841 } 10842 10843 10844 /++ 10845 You can use `mixin(dumpParams);` to put out a debug print of your current function call w/ params. 10846 +/ 10847 enum string dumpParams = q{ 10848 { 10849 import arsd.core; 10850 arsd.core.dumpParamsImpl(__FUNCTION__, __traits(parameters)); 10851 } 10852 }; 10853 10854 /// Don't call this directly, use `mixin(dumpParams);` instead 10855 public void dumpParamsImpl(T...)(string func, T args) { 10856 char[256] bufferBacking; 10857 writeGuts(bufferBacking[], func ~ "(", ")\n", ", ", false, true, &actuallyWriteToStdout, args); 10858 } 10859 10860 /++ 10861 A `writeln` (and friends) that actually works, at least for some basic types. 10862 10863 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. 10864 10865 This always does text. See also WritableStream and WritableTextStream when they are implemented. 10866 +/ 10867 void writeln(T...)(T t) { 10868 char[256] bufferBacking; 10869 writeGuts(bufferBacking[], null, "\n", null, false, false, &actuallyWriteToStdout, t); 10870 } 10871 10872 /// ditto 10873 alias writelnStdOut = writeln; 10874 10875 /// ditto 10876 void writelnStderr(T...)(T t) { 10877 char[256] bufferBacking; 10878 writeGuts(bufferBacking[], null, "\n", null, false, false, &actuallyWriteToStderr, t); 10879 } 10880 10881 /// ditto 10882 void writeStdout(T...)(T t) { 10883 char[256] bufferBacking; 10884 writeGuts(bufferBacking[], null, null, null, false, false, &actuallyWriteToStdout, t); 10885 } 10886 10887 /// ditto 10888 void writeStderr(T...)(T t) { 10889 char[256] bufferBacking; 10890 writeGuts(bufferBacking[], null, null, null, false, false, &actuallyWriteToStderr, t); 10891 } 10892 10893 struct ValueWithFormattingArgs(T : double) { 10894 double value; 10895 FloatToStringArgs args; 10896 } 10897 10898 struct ValueWithFormattingArgs(T : long) { 10899 long value; 10900 IntToStringArgs args; 10901 } 10902 10903 ValueWithFormattingArgs!double formatArgs(double value, FloatToStringArgs args) { 10904 return ValueWithFormattingArgs!double(value, args); 10905 } 10906 ValueWithFormattingArgs!double formatArgs(double value, int precision) { 10907 return ValueWithFormattingArgs!double(value, FloatToStringArgs().withPrecision(precision)); 10908 10909 } 10910 10911 unittest { 10912 assert(toStringInternal(5.4364.formatArgs(FloatToStringArgs().withPrecision(2))) == "5.44"); 10913 assert(toStringInternal(5.4364.formatArgs(precision: 2)) == "5.44"); 10914 } 10915 10916 /++ 10917 10918 +/ 10919 package(arsd) string enumNameForValue(T)(T t) { 10920 switch(t) { 10921 foreach(memberName; __traits(allMembers, T)) { 10922 case __traits(getMember, T, memberName): 10923 return memberName; 10924 } 10925 default: 10926 return "<unknown>"; 10927 } 10928 } 10929 10930 /+ 10931 Purposes: 10932 * debugging 10933 * writing 10934 * converting single value to string? 10935 +/ 10936 private string writeGuts(T...)(char[] buffer, string prefix, string suffix, string argSeparator, bool printInterpolatedCode, bool quoteStrings, string function(scope char[] result) writer, T t) { 10937 int pos; 10938 10939 if(prefix.length) 10940 appendToBuffer(buffer, pos, prefix); 10941 10942 foreach(i, arg; t) { 10943 static if(i) 10944 if(argSeparator.length) 10945 appendToBuffer(buffer, pos, argSeparator); 10946 10947 static if(is(typeof(arg) == InterpolatedExpression!code, string code)) { 10948 if(printInterpolatedCode) { 10949 appendToBuffer(buffer, pos, code); 10950 appendToBuffer(buffer, pos, " = "); 10951 } 10952 } else { 10953 writeIndividualArg(buffer, pos, quoteStrings, arg); 10954 } 10955 } 10956 10957 if(suffix.length) 10958 appendToBuffer(buffer, pos, suffix); 10959 10960 return writer(buffer[0 .. pos]); 10961 } 10962 10963 private void writeIndividualArg(T)(ref char[] buffer, ref int pos, bool quoteStrings, T arg) { 10964 static if(is(typeof(arg) == ValueWithFormattingArgs!V, V)) { 10965 appendToBuffer(buffer, pos, arg.value, arg.args); 10966 } else static if(is(typeof(arg) Base == enum)) { 10967 appendToBuffer(buffer, pos, typeof(arg).stringof); 10968 appendToBuffer(buffer, pos, "."); 10969 appendToBuffer(buffer, pos, enumNameForValue(arg)); 10970 appendToBuffer(buffer, pos, "("); 10971 appendToBuffer(buffer, pos, cast(Base) arg); 10972 appendToBuffer(buffer, pos, ")"); 10973 } else static if(is(typeof(arg) : const char[])) { 10974 if(quoteStrings) { 10975 appendToBuffer(buffer, pos, "\""); 10976 appendToBuffer(buffer, pos, arg); // FIXME: escape quote and backslash in there? 10977 appendToBuffer(buffer, pos, "\""); 10978 } else { 10979 appendToBuffer(buffer, pos, arg); 10980 } 10981 } else static if(is(typeof(arg) : stringz)) { 10982 appendToBuffer(buffer, pos, arg.borrow); 10983 } else static if(is(typeof(arg) : long)) { 10984 appendToBuffer(buffer, pos, arg); 10985 } else static if(is(typeof(arg) : double)) { 10986 appendToBuffer(buffer, pos, arg); 10987 } else static if(is(typeof(arg.toString()) : const char[])) { 10988 appendToBuffer(buffer, pos, arg.toString()); 10989 } else static if(is(typeof(arg) A == struct)) { 10990 appendToBuffer(buffer, pos, A.stringof); 10991 appendToBuffer(buffer, pos, "("); 10992 foreach(idx, item; arg.tupleof) { 10993 if(idx) 10994 appendToBuffer(buffer, pos, ", "); 10995 appendToBuffer(buffer, pos, __traits(identifier, arg.tupleof[idx])); 10996 appendToBuffer(buffer, pos, ": "); 10997 writeIndividualArg(buffer, pos, true, item); 10998 } 10999 appendToBuffer(buffer, pos, ")"); 11000 } else static if(is(typeof(arg) == E[], E)) { 11001 appendToBuffer(buffer, pos, "["); 11002 foreach(idx, item; arg) { 11003 if(idx) 11004 appendToBuffer(buffer, pos, ", "); 11005 writeIndividualArg(buffer, pos, true, item); 11006 } 11007 appendToBuffer(buffer, pos, "]"); 11008 } else static if(is(typeof(arg) == delegate)) { 11009 appendToBuffer(buffer, pos, "<" ~ typeof(arg).stringof ~ "> "); 11010 appendToBuffer(buffer, pos, cast(size_t) arg.ptr, IntToStringArgs().withRadix(16).withPadding(12, '0')); 11011 appendToBuffer(buffer, pos, ", "); 11012 appendToBuffer(buffer, pos, cast(size_t) arg.funcptr, IntToStringArgs().withRadix(16).withPadding(12, '0')); 11013 } else static if(is(typeof(arg) : const void*)) { 11014 appendToBuffer(buffer, pos, "<" ~ typeof(arg).stringof ~ "> "); 11015 appendToBuffer(buffer, pos, cast(size_t) arg, IntToStringArgs().withRadix(16).withPadding(12, '0')); 11016 } else { 11017 appendToBuffer(buffer, pos, "<" ~ typeof(arg).stringof ~ ">"); 11018 } 11019 } 11020 11021 debug string inspect(T)(T t, string varName = null, int indent = 0) { 11022 string str; 11023 foreach(i; 0 .. indent) 11024 str ~= "\t"; 11025 if(varName.length) { 11026 str ~= varName; 11027 str ~= ": "; 11028 } 11029 str ~= T.stringof; 11030 str ~= "("; 11031 // hack for phobos nullable 11032 static if(is(T == struct) && __traits(identifier, T) == "Nullable") { 11033 if(t.isNull) { 11034 str ~= "null)"; 11035 } else { 11036 str ~= "\n"; 11037 str ~= inspect(t.get(), null, indent + 1); 11038 foreach(i; 0 .. indent) 11039 str ~= "\t"; 11040 str ~= ")"; 11041 } 11042 } 11043 else 11044 // generic inspection 11045 static if(is(T == class) || is(T == struct) || is(T == interface)) { 11046 str ~= "\n"; 11047 foreach(memberName; __traits(allMembers, T)) 11048 static if(is(typeof(__traits(getMember, t, memberName).offsetof))) 11049 { 11050 str ~= inspect(__traits(getMember, t, memberName), memberName, indent + 1); 11051 } 11052 foreach(i; 0 .. indent) 11053 str ~= "\t"; 11054 str ~= ")"; 11055 } else { 11056 str ~= toStringInternal(t); 11057 str ~= ")"; 11058 } 11059 11060 str ~= "\n"; 11061 11062 return str; 11063 } 11064 11065 debug void dump(T...)(T t, string file = __FILE__, size_t line = __LINE__) { 11066 string separator; 11067 static if(T.length && is(T[0] == InterpolationHeader)) 11068 separator = null; 11069 else 11070 separator = "; "; 11071 11072 char[256] bufferBacking; 11073 writeGuts(bufferBacking[], file ~ ":" ~ toStringInternal(line) ~ ": ", "\n", separator, true, true, &actuallyWriteToStdout, t); 11074 } 11075 11076 private string makeString(scope char[] buffer) @safe { 11077 return buffer.idup; 11078 } 11079 private string makeStringCasting(scope return char[] buffer) @system @nogc nothrow pure { 11080 return cast(string) buffer; 11081 } 11082 private string actuallyWriteToStdout(scope char[] buffer) @safe { 11083 return actuallyWriteToStdHandle(1, buffer); 11084 } 11085 private string actuallyWriteToStderr(scope char[] buffer) @safe { 11086 return actuallyWriteToStdHandle(2, buffer); 11087 } 11088 private string actuallyWriteToStdHandle(int whichOne, scope char[] buffer) @trusted { 11089 version(UseStdioWriteln) 11090 { 11091 import std.stdio; 11092 (whichOne == 1 ? stdout : stderr).writeln(buffer); 11093 } 11094 else version(Windows) { 11095 import core.sys.windows.wincon; 11096 11097 auto h = whichOne == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE; 11098 11099 auto hStdOut = GetStdHandle(h); 11100 if(hStdOut == null || hStdOut == INVALID_HANDLE_VALUE) { 11101 AllocConsole(); 11102 hStdOut = GetStdHandle(h); 11103 } 11104 11105 if(GetFileType(hStdOut) == FILE_TYPE_CHAR) { 11106 WCharzBuffer toWrite = WCharzBuffer(buffer, WindowsStringConversionFlags.convertNewLines); 11107 11108 DWORD written; 11109 WriteConsoleW(hStdOut, toWrite.ptr, cast(DWORD) toWrite.length, &written, null); 11110 } else { 11111 DWORD written; 11112 WriteFile(hStdOut, buffer.ptr, cast(DWORD) buffer.length, &written, null); 11113 } 11114 } else { 11115 import unix = core.sys.posix.unistd; 11116 unix.write(whichOne, buffer.ptr, buffer.length); 11117 } 11118 11119 return null; 11120 } 11121 11122 /++ 11123 As the C function it calls, this is not thread safe. 11124 11125 Returns: 11126 `null` if `name` not found. Note this is distinct from an empty string. 11127 +/ 11128 string getEnvironmentVariable(scope const(char)[] name) { 11129 version(Posix) { 11130 import core.stdc.stdlib; 11131 CharzBuffer namez = name; 11132 auto e = getenv(namez.ptr); 11133 if(e is null) 11134 return null; 11135 return stringz(e).borrow.idup; 11136 } else version(Windows) { 11137 WCharzBuffer namew = name; 11138 wchar[128] staticBuffer; 11139 wchar[] buffer = staticBuffer; 11140 auto ret = GetEnvironmentVariableW(namew.ptr, buffer.ptr, cast(DWORD) buffer.length); 11141 if(ret > buffer.length) { 11142 buffer.length = ret; 11143 ret = GetEnvironmentVariableW(namew.ptr, buffer.ptr, cast(DWORD) buffer.length); 11144 } 11145 if(ret == 0) { 11146 auto err = GetLastError(); 11147 if(err == ERROR_SUCCESS) { 11148 return ""; 11149 } else if(err == ERROR_ENVVAR_NOT_FOUND) { 11150 return null; 11151 } else { 11152 throw new WindowsApiException("GetEnvironmentVariable", err); 11153 } 11154 } 11155 11156 return makeUtf8StringFromWindowsString(buffer[0 .. ret]); 11157 } else static assert(0); 11158 } 11159 11160 /+ 11161 11162 STDIO 11163 11164 /++ 11165 Please note using this will create a compile-time dependency on [arsd.terminal] 11166 11167 11168 11169 so my writeln replacement: 11170 11171 1) if the std output handle is null, alloc one 11172 2) if it is a character device, write out the proper Unicode text. 11173 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... 11174 [8:15 AM] 11175 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 11176 [8:16 AM] 11177 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 11178 11179 Stdout can represent either 11180 11181 +/ 11182 void writeln(){} { 11183 11184 } 11185 11186 stderr? 11187 11188 /++ 11189 Please note using this will create a compile-time dependency on [arsd.terminal] 11190 11191 It can be called from a task. 11192 11193 It works correctly on Windows and is user friendly on Linux (using arsd.terminal.getline) 11194 while also working if stdin has been redirected (where arsd.terminal itself would throw) 11195 11196 11197 so say you run a program on an interactive terminal. the program tries to open the stdin binary stream 11198 11199 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 11200 11201 +/ 11202 string readln()() { 11203 11204 } 11205 11206 11207 // if using stdio as a binary output thing you can pretend it is a file w/ stream capability 11208 struct File { 11209 WritableStream ostream; 11210 ReadableStream istream; 11211 11212 ulong tell; 11213 void seek(ulong to) {} 11214 11215 void sync(); 11216 void close(); 11217 } 11218 11219 // 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. 11220 WritableStream stdoutStream() { return null; } 11221 WritableStream stderrStream() { return null; } 11222 ReadableStream stdinStream() { return null; } 11223 11224 +/ 11225 11226 11227 /+ 11228 11229 11230 /+ 11231 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. 11232 +/ 11233 11234 /+ 11235 11236 arsd_core_init(number_of_worker_threads) 11237 11238 Building-block things wanted for the event loop integration: 11239 * ui 11240 * windows 11241 * terminal / console 11242 * generic 11243 * adopt fd 11244 * adopt windows handle 11245 * shared lib 11246 * load 11247 * timers (relative and real time) 11248 * create 11249 * update 11250 * cancel 11251 * file/directory watches 11252 * file created 11253 * file deleted 11254 * file modified 11255 * file ops 11256 * open 11257 * close 11258 * read 11259 * write 11260 * seek 11261 * sendfile on linux, TransmitFile on Windows 11262 * let completion handlers run in the io worker thread instead of signaling back 11263 * pipe ops (anonymous or named) 11264 * create 11265 * read 11266 * write 11267 * get info about other side of the pipe 11268 * network ops (stream + datagram, ip, ipv6, unix) 11269 * address look up 11270 * connect 11271 * start tls 11272 * listen 11273 * send 11274 * receive 11275 * get peer info 11276 * process ops 11277 * spawn 11278 * notifications when it is terminated or fork or execs 11279 * send signal 11280 * i/o pipes 11281 * thread ops (isDaemon?) 11282 * spawn 11283 * talk to its event loop 11284 * termination notification 11285 * signals 11286 * 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. 11287 * custom messages 11288 * should be able to send messages from finalizers... 11289 11290 * want to make sure i can stream stuff on top of it all too. 11291 11292 ======== 11293 11294 These things all refer back to a task-local thing that queues the tasks. If it is a fiber, it uses that 11295 and if it is a thread it uses that... 11296 11297 tls IArsdCoreEventLoop curentTaskInterface; // this yields on the wait for calls. the fiber swapper will swap this too. 11298 tls IArsdCoreEventLoop currentThreadInterface; // this blocks on the event loop 11299 11300 shared IArsdCoreEventLoop currentProcessInterface; // this dispatches to any available thread 11301 +/ 11302 11303 11304 /+ 11305 You might have configurable tasks that do not auto-start, e.g. httprequest. maybe @mustUse on those 11306 11307 then some that do auto-start, e.g. setTimeout 11308 11309 11310 timeouts: duration, MonoTime, or SysTime? duration is just a timer monotime auto-adjusts the when, systime sets a real time timerfd 11311 11312 tasks can be set to: 11313 thread affinity - this, any, specific reference 11314 reports to - defaults to this, can also pass down a parent reference. if reports to dies, all its subordinates are cancelled. 11315 11316 11317 you can send a message to a task... maybe maybe just to a task runner (which is itself a task?) 11318 11319 auto file = readFile(x); 11320 auto timeout = setTimeout(y); 11321 auto completed = waitForFirstToCompleteThenCancelOthers(file, timeout); 11322 if(completed == 0) { 11323 file.... 11324 } else { 11325 timeout.... 11326 } 11327 11328 /+ 11329 A task will run on a thread (with possible migration), and report to a task. 11330 +/ 11331 11332 // a compute task is run on a helper thread 11333 auto task = computeTask((shared(bool)* cancellationRequested) { 11334 // 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) 11335 11336 // you'd periodically send messages back to the parent 11337 }, RunOn.AnyAvailable, Affinity.CanMigrate); 11338 11339 auto task = Task((TaskController controller) { 11340 foreach(x, 0 .. 1000) { 11341 if(x % 10 == 0) 11342 controller.yield(); // periodically yield control, which also checks for cancellation for us 11343 // do some work 11344 11345 controller.sendMessage(...); 11346 controller.sendProgress(x); // yields it for a foreach stream kind of thing 11347 } 11348 11349 return something; // automatically sends the something as the result in a TaskFinished message 11350 }); 11351 11352 foreach(item; task) // waitsForProgress, sendProgress sends an item and the final return sends an item 11353 {} 11354 11355 11356 see ~/test/task.d 11357 11358 // an io task is run locally via the event loops 11359 auto task2 = ioTask(() { 11360 11361 }); 11362 11363 11364 11365 waitForEvent 11366 +/ 11367 11368 /+ 11369 Most functions should prolly take a thread arg too, which defaults 11370 to this thread, but you can also pass it a reference, or a "any available" thing. 11371 11372 This can be a ufcs overload 11373 +/ 11374 11375 interface SemiSynchronousTask { 11376 11377 } 11378 11379 struct TimeoutCompletionResult { 11380 bool completed; 11381 11382 bool opCast(T : bool)() { 11383 return completed; 11384 } 11385 } 11386 11387 struct Timeout { 11388 void reschedule(Duration when) { 11389 11390 } 11391 11392 void cancel() { 11393 11394 } 11395 11396 TimeoutCompletionResult waitForCompletion() { 11397 return TimeoutCompletionResult(false); 11398 } 11399 } 11400 11401 Timeout setTimeout(void delegate() dg, int msecs, int permittedJitter = 20) { 11402 static assert(0); 11403 return Timeout.init; 11404 } 11405 11406 void clearTimeout(Timeout timeout) { 11407 timeout.cancel(); 11408 } 11409 11410 void createInterval() {} 11411 void clearInterval() {} 11412 11413 /++ 11414 Schedules a task at the given wall clock time. 11415 +/ 11416 void scheduleTask() {} 11417 11418 struct IoOperationCompletionResult { 11419 enum Status { 11420 cancelled, 11421 completed 11422 } 11423 11424 Status status; 11425 11426 int error; 11427 int bytesWritten; 11428 11429 bool opCast(T : bool)() { 11430 return status == Status.completed; 11431 } 11432 } 11433 11434 struct IoOperation { 11435 void cancel() {} 11436 11437 IoOperationCompletionResult waitForCompletion() { 11438 return IoOperationCompletionResult.init; 11439 } 11440 11441 // could contain a scoped class in here too so it stack allocated 11442 } 11443 11444 // Should return both the object and the index in the array! 11445 Result waitForFirstToComplete(Operation[]...) {} 11446 11447 IoOperation read(IoHandle handle, ubyte[] buffer 11448 11449 /+ 11450 class IoOperation {} 11451 11452 // an io operation and its buffer must not be modified or freed 11453 // in between a call to enqueue and a call to waitForCompletion 11454 // if you used the whenComplete callback, make sure it is NOT gc'd or scope thing goes out of scope in the mean time 11455 // if its dtor runs, it'd be forced to be cancelled... 11456 11457 scope IoOperation op = new IoOperation(buffer_size); 11458 op.start(); 11459 op.waitForCompletion(); 11460 +/ 11461 11462 /+ 11463 will want: 11464 read, write 11465 send, recv 11466 11467 cancel 11468 11469 open file, open (named or anonymous) pipe, open process 11470 connect, accept 11471 SSL 11472 close 11473 11474 postEvent 11475 postAPC? like run in gui thread / async 11476 waitForEvent ? needs to handle a timeout and a cancellation. would only work in the fiber task api. 11477 11478 waitForSuccess 11479 11480 interrupt handler 11481 11482 onPosixReadReadiness 11483 onPosixWriteReadiness 11484 11485 onWindowsHandleReadiness 11486 - but they're one-offs so you gotta reregister for each event 11487 +/ 11488 11489 11490 11491 /+ 11492 arsd.core.uda 11493 11494 you define a model struct with the types you want to extract 11495 11496 you get it with like Model extract(Model, UDAs...)(Model default) 11497 11498 defaultModel!alias > defaultModel!Type(defaultModel("identifier")) 11499 11500 11501 11502 11503 11504 11505 11506 11507 11508 11509 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 11510 11511 you might be like 11512 11513 struct MyUdas { 11514 DbName name; 11515 DbIgnore ignore; 11516 } 11517 11518 elsewhere 11519 11520 foreach(alias; allMembers) { 11521 auto udas = getUdas!(MyUdas, __traits(getAttributes, alias))(MyUdas(DbName(__traits(identifier, alias)))); 11522 } 11523 11524 11525 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 11526 11527 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 11528 11529 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 11530 +/ 11531 11532 +/ 11533 11534 package(arsd) version(Windows) extern(Windows) { 11535 BOOL CancelIoEx(HANDLE, LPOVERLAPPED); 11536 11537 struct WSABUF { 11538 ULONG len; 11539 ubyte* buf; 11540 } 11541 alias LPWSABUF = WSABUF*; 11542 11543 // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/ns-winsock2-wsaoverlapped 11544 // "The WSAOVERLAPPED structure is compatible with the Windows OVERLAPPED structure." 11545 // so ima lie here in the bindings. 11546 11547 int WSASend(SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 11548 int WSASendTo(SOCKET, LPWSABUF, DWORD, LPDWORD, DWORD, const sockaddr*, int, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 11549 11550 int WSARecv(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 11551 int WSARecvFrom(SOCKET, LPWSABUF, DWORD, LPDWORD, LPDWORD, sockaddr*, LPINT, LPOVERLAPPED, LPOVERLAPPED_COMPLETION_ROUTINE); 11552 } 11553 11554 package(arsd) version(UseCocoa) { 11555 11556 /* Copy/paste chunk from Jacob Carlborg { */ 11557 // from https://raw.githubusercontent.com/jacob-carlborg/druntime/550edd0a64f0eb2c4f35d3ec3d88e26b40ac779e/src/core/stdc/clang_block.d 11558 // with comments stripped (see docs in the original link), code reformatted, and some names changed to avoid potential conflicts 11559 11560 // note these should always be passed by pointer! 11561 11562 import core.stdc.config; 11563 struct ObjCBlock(R = void, Params...) { 11564 private: 11565 alias extern(C) R function(ObjCBlock*, Params) Invoke; 11566 11567 void* isa; 11568 int flags; 11569 int reserved = 0; 11570 Invoke invoke; 11571 Descriptor* descriptor; 11572 11573 // Imported variables go here 11574 R delegate(Params) dg; 11575 11576 this(void* isa, int flags, Invoke invoke, R delegate(Params) dg) { 11577 this.isa = isa; 11578 this.flags = flags; 11579 this.invoke = invoke; 11580 this.descriptor = &.objcblock_descriptor; 11581 11582 // FIXME: is this needed or not? it could be held by the OS and not be visible to GC i think 11583 // import core.memory; GC.addRoot(dg.ptr); 11584 11585 this.dg = dg; 11586 } 11587 } 11588 ObjCBlock!(R, Params) blockOnStack(R, Params...)(R delegate(Params) dg) { 11589 static if (Params.length == 0) 11590 enum flags = 0x50000000; 11591 else 11592 enum flags = 0x40000000; 11593 11594 return ObjCBlock!(R, Params)(&_NSConcreteStackBlock, flags, &objcblock_invoke!(R, Params), dg); 11595 } 11596 ObjCBlock!(R, Params)* block(R, Params...)(R delegate(Params) dg) { 11597 static if (Params.length == 0) 11598 enum flags = 0x50000000; 11599 else 11600 enum flags = 0x40000000; 11601 11602 return new ObjCBlock!(R, Params)(&_NSConcreteStackBlock, flags, &objcblock_invoke!(R, Params), dg); 11603 } 11604 11605 private struct Descriptor { 11606 c_ulong reserved; 11607 c_ulong size; 11608 const(char)* signature; 11609 } 11610 private extern(C) extern __gshared void*[32] _NSConcreteStackBlock; 11611 private __gshared auto objcblock_descriptor = Descriptor(0, ObjCBlock!().sizeof); 11612 private extern(C) R objcblock_invoke(R, Args...)(ObjCBlock!(R, Args)* block, Args args) { 11613 return block.dg(args); 11614 } 11615 11616 11617 /* End copy/paste chunk from Jacob Carlborg } */ 11618 11619 11620 /+ 11621 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. 11622 11623 If you are not sure if Cocoa thinks your application is multithreaded or not, you can use the isMultiThreaded method of NSThread to check. 11624 +/ 11625 11626 11627 struct DeifiedNSString { 11628 char[16] sso; 11629 const(char)[] str; 11630 11631 this(NSString s) { 11632 auto len = s.length; 11633 if(len <= sso.length / 4) 11634 str = sso[]; 11635 else 11636 str = new char[](len * 4); 11637 11638 NSUInteger count; 11639 NSRange leftover; 11640 auto ret = s.getBytes(cast(char*) str.ptr, str.length, &count, NSStringEncoding.NSUTF8StringEncoding, NSStringEncodingConversionOptions.none, NSRange(0, len), &leftover); 11641 if(ret) 11642 str = str[0 .. count]; 11643 else 11644 throw new Exception("uh oh"); 11645 } 11646 } 11647 11648 extern (Objective-C) { 11649 import core.attribute; // : selector, optional; 11650 11651 alias NSUInteger = size_t; 11652 alias NSInteger = ptrdiff_t; 11653 alias unichar = wchar; 11654 struct SEL_; 11655 alias SEL_* SEL; 11656 // 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. 11657 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 11658 11659 extern class NSObject { 11660 static NSObject alloc() @selector("alloc"); 11661 NSObject init() @selector("init"); 11662 11663 void retain() @selector("retain"); 11664 void release() @selector("release"); 11665 void autorelease() @selector("autorelease"); 11666 11667 void performSelectorOnMainThread(SEL aSelector, NSid arg, bool waitUntilDone) @selector("performSelectorOnMainThread:withObject:waitUntilDone:"); 11668 } 11669 11670 // this is some kind of generic in objc... 11671 extern class NSArray : NSObject { 11672 static NSArray arrayWithObjects(NSid* objects, NSUInteger count) @selector("arrayWithObjects:count:"); 11673 } 11674 11675 extern class NSString : NSObject { 11676 override static NSString alloc() @selector("alloc"); 11677 override NSString init() @selector("init"); 11678 11679 NSString initWithUTF8String(const scope char* str) @selector("initWithUTF8String:"); 11680 11681 NSString initWithBytes( 11682 const(ubyte)* bytes, 11683 NSUInteger length, 11684 NSStringEncoding encoding 11685 ) @selector("initWithBytes:length:encoding:"); 11686 11687 unichar characterAtIndex(NSUInteger index) @selector("characterAtIndex:"); 11688 NSUInteger length() @selector("length"); 11689 const char* UTF8String() @selector("UTF8String"); 11690 11691 void getCharacters(wchar* buffer, NSRange range) @selector("getCharacters:range:"); 11692 11693 bool getBytes(void* buffer, NSUInteger maxBufferCount, NSUInteger* usedBufferCount, NSStringEncoding encoding, NSStringEncodingConversionOptions options, NSRange range, NSRange* leftover) @selector("getBytes:maxLength:usedLength:encoding:options:range:remainingRange:"); 11694 11695 CGSize sizeWithAttributes(NSDictionary attrs) @selector("sizeWithAttributes:"); 11696 } 11697 11698 // FIXME: it is a generic in objc with <KeyType, ObjectType> 11699 extern class NSDictionary : NSObject { 11700 static NSDictionary dictionaryWithObject(NSObject object, NSid key) @selector("dictionaryWithObject:forKey:"); 11701 // static NSDictionary initWithObjects(NSArray objects, NSArray forKeys) @selector("initWithObjects:forKeys:"); 11702 } 11703 11704 alias NSAttributedStringKey = NSString; 11705 /* const */extern __gshared NSAttributedStringKey NSFontAttributeName; 11706 11707 struct NSRange { 11708 NSUInteger loc; 11709 NSUInteger len; 11710 } 11711 11712 enum NSStringEncodingConversionOptions : NSInteger { 11713 none = 0, 11714 NSAllowLossyEncodingConversion = 1, 11715 NSExternalRepresentationEncodingConversion = 2 11716 } 11717 11718 enum NSEventType { 11719 idk 11720 11721 } 11722 11723 enum NSEventModifierFlags : NSUInteger { 11724 NSEventModifierFlagCapsLock = 1 << 16, 11725 NSEventModifierFlagShift = 1 << 17, 11726 NSEventModifierFlagControl = 1 << 18, 11727 NSEventModifierFlagOption = 1 << 19, // aka Alt 11728 NSEventModifierFlagCommand = 1 << 20, // aka super 11729 NSEventModifierFlagNumericPad = 1 << 21, 11730 NSEventModifierFlagHelp = 1 << 22, 11731 NSEventModifierFlagFunction = 1 << 23, 11732 NSEventModifierFlagDeviceIndependentFlagsMask = 0xffff0000UL 11733 } 11734 11735 version(OSX) 11736 extern class NSEvent : NSObject { 11737 NSEventType type() @selector("type"); 11738 11739 NSPoint locationInWindow() @selector("locationInWindow"); 11740 NSTimeInterval timestamp() @selector("timestamp"); 11741 NSWindow window() @selector("window"); // note: nullable 11742 NSEventModifierFlags modifierFlags() @selector("modifierFlags"); 11743 11744 NSString characters() @selector("characters"); 11745 NSString charactersIgnoringModifiers() @selector("charactersIgnoringModifiers"); 11746 ushort keyCode() @selector("keyCode"); 11747 ushort specialKey() @selector("specialKey"); 11748 11749 static NSUInteger pressedMouseButtons() @selector("pressedMouseButtons"); 11750 NSPoint locationInWindow() @selector("locationInWindow"); // in screen coordinates 11751 static NSPoint mouseLocation() @selector("mouseLocation"); // in screen coordinates 11752 NSInteger buttonNumber() @selector("buttonNumber"); 11753 11754 CGFloat deltaX() @selector("deltaX"); 11755 CGFloat deltaY() @selector("deltaY"); 11756 CGFloat deltaZ() @selector("deltaZ"); 11757 11758 bool hasPreciseScrollingDeltas() @selector("hasPreciseScrollingDeltas"); 11759 11760 CGFloat scrollingDeltaX() @selector("scrollingDeltaX"); 11761 CGFloat scrollingDeltaY() @selector("scrollingDeltaY"); 11762 11763 // @property(getter=isDirectionInvertedFromDevice, readonly) BOOL directionInvertedFromDevice; 11764 } 11765 11766 extern /* final */ class NSTimer : NSObject { // the docs say don't subclass this, but making it final breaks the bridge 11767 override static NSTimer alloc() @selector("alloc"); 11768 override NSTimer init() @selector("init"); 11769 11770 static NSTimer schedule(NSTimeInterval timeIntervalInSeconds, NSid target, SEL selector, NSid userInfo, bool repeats) @selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"); 11771 11772 void fire() @selector("fire"); 11773 void invalidate() @selector("invalidate"); 11774 11775 bool valid() @selector("isValid"); 11776 // @property(copy) NSDate *fireDate; 11777 NSTimeInterval timeInterval() @selector("timeInterval"); 11778 NSid userInfo() @selector("userInfo"); 11779 11780 NSTimeInterval tolerance() @selector("tolerance"); 11781 NSTimeInterval tolerance(NSTimeInterval) @selector("setTolerance:"); 11782 } 11783 11784 alias NSTimeInterval = double; 11785 11786 version(OSX) 11787 extern class NSResponder : NSObject { 11788 NSMenu menu() @selector("menu"); 11789 void menu(NSMenu menu) @selector("setMenu:"); 11790 11791 void keyDown(NSEvent event) @selector("keyDown:"); 11792 void keyUp(NSEvent event) @selector("keyUp:"); 11793 11794 // - (void)interpretKeyEvents:(NSArray<NSEvent *> *)eventArray; 11795 11796 void mouseDown(NSEvent event) @selector("mouseDown:"); 11797 void mouseDragged(NSEvent event) @selector("mouseDragged:"); 11798 void mouseUp(NSEvent event) @selector("mouseUp:"); 11799 void mouseMoved(NSEvent event) @selector("mouseMoved:"); 11800 void mouseEntered(NSEvent event) @selector("mouseEntered:"); 11801 void mouseExited(NSEvent event) @selector("mouseExited:"); 11802 11803 void rightMouseDown(NSEvent event) @selector("rightMouseDown:"); 11804 void rightMouseDragged(NSEvent event) @selector("rightMouseDragged:"); 11805 void rightMouseUp(NSEvent event) @selector("rightMouseUp:"); 11806 11807 void otherMouseDown(NSEvent event) @selector("otherMouseDown:"); 11808 void otherMouseDragged(NSEvent event) @selector("otherMouseDragged:"); 11809 void otherMouseUp(NSEvent event) @selector("otherMouseUp:"); 11810 11811 void scrollWheel(NSEvent event) @selector("scrollWheel:"); 11812 11813 // touch events should also be here btw among others 11814 } 11815 11816 version(OSX) 11817 extern class NSApplication : NSResponder { 11818 static NSApplication shared_() @selector("sharedApplication"); 11819 11820 NSApplicationDelegate delegate_() @selector("delegate"); 11821 void delegate_(NSApplicationDelegate) @selector("setDelegate:"); 11822 11823 bool setActivationPolicy(NSApplicationActivationPolicy activationPolicy) @selector("setActivationPolicy:"); 11824 11825 void activateIgnoringOtherApps(bool flag) @selector("activateIgnoringOtherApps:"); 11826 11827 @property NSMenu mainMenu() @selector("mainMenu"); 11828 @property NSMenu mainMenu(NSMenu) @selector("setMainMenu:"); 11829 11830 void run() @selector("run"); 11831 11832 void stop(NSid sender) @selector("stop:"); 11833 11834 void finishLaunching() @selector("finishLaunching"); 11835 11836 void terminate(void*) @selector("terminate:"); 11837 11838 void sendEvent(NSEvent event) @selector("sendEvent:"); 11839 NSEvent nextEventMatchingMask( 11840 NSEventMask mask, 11841 NSDate untilDate, 11842 NSRunLoopMode inMode, 11843 bool dequeue 11844 ) @selector("nextEventMatchingMask:untilDate:inMode:dequeue:"); 11845 } 11846 11847 enum NSEventMask : ulong { 11848 NSEventMaskAny = ulong.max 11849 } 11850 11851 version(OSX) 11852 extern class NSRunLoop : NSObject { 11853 static @property NSRunLoop currentRunLoop() @selector("currentRunLoop"); 11854 static @property NSRunLoop mainRunLoop() @selector("mainRunLoop"); 11855 bool runMode(NSRunLoopMode mode, NSDate beforeDate) @selector("runMode:beforeDate:"); 11856 } 11857 11858 alias NSRunLoopMode = NSString; 11859 11860 extern __gshared NSRunLoopMode NSDefaultRunLoopMode; 11861 11862 version(OSX) 11863 extern class NSDate : NSObject { 11864 static @property NSDate distantFuture() @selector("distantFuture"); 11865 static @property NSDate distantPast() @selector("distantPast"); 11866 static @property NSDate now() @selector("now"); 11867 11868 } 11869 11870 version(OSX) 11871 extern interface NSApplicationDelegate { 11872 void applicationWillFinishLaunching(NSNotification notification) @selector("applicationWillFinishLaunching:"); 11873 void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:"); 11874 bool applicationShouldTerminateAfterLastWindowClosed(NSNotification notification) @selector("applicationShouldTerminateAfterLastWindowClosed:"); 11875 } 11876 11877 extern class NSNotification : NSObject { 11878 @property NSid object() @selector("object"); 11879 } 11880 11881 enum NSApplicationActivationPolicy : ptrdiff_t { 11882 /* 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. */ 11883 regular, 11884 11885 /* 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. */ 11886 accessory, 11887 11888 /* 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. */ 11889 prohibited 11890 } 11891 11892 extern class NSGraphicsContext : NSObject { 11893 static NSGraphicsContext currentContext() @selector("currentContext"); 11894 NSGraphicsContext graphicsPort() @selector("graphicsPort"); 11895 } 11896 11897 version(OSX) 11898 extern class NSMenu : NSObject { 11899 override static NSMenu alloc() @selector("alloc"); 11900 11901 override NSMenu init() @selector("init"); 11902 NSMenu init(NSString title) @selector("initWithTitle:"); 11903 11904 void setSubmenu(NSMenu menu, NSMenuItem item) @selector("setSubmenu:forItem:"); 11905 void addItem(NSMenuItem newItem) @selector("addItem:"); 11906 11907 NSMenuItem addItem( 11908 NSString title, 11909 SEL selector, 11910 NSString charCode 11911 ) @selector("addItemWithTitle:action:keyEquivalent:"); 11912 } 11913 11914 version(OSX) 11915 extern class NSMenuItem : NSObject { 11916 override static NSMenuItem alloc() @selector("alloc"); 11917 override NSMenuItem init() @selector("init"); 11918 11919 NSMenuItem init( 11920 NSString title, 11921 SEL selector, 11922 NSString charCode 11923 ) @selector("initWithTitle:action:keyEquivalent:"); 11924 11925 void enabled(bool) @selector("setEnabled:"); 11926 11927 NSResponder target(NSResponder) @selector("setTarget:"); 11928 } 11929 11930 enum NSWindowStyleMask : size_t { 11931 borderless = 0, 11932 titled = 1 << 0, 11933 closable = 1 << 1, 11934 miniaturizable = 1 << 2, 11935 resizable = 1 << 3, 11936 11937 /* 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. 11938 */ 11939 texturedBackground = 1 << 8, 11940 11941 /* 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. 11942 */ 11943 unifiedTitleAndToolbar = 1 << 12, 11944 11945 /* When set, the window will appear full screen. This mask is automatically toggled when toggleFullScreen: is called. 11946 */ 11947 fullScreen = 1 << 14, 11948 11949 /* 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. 11950 Utilizing this mask opts-in to layer-backing. Utilize the contentLayoutRect or auto-layout contentLayoutGuide to layout views underneath the titlebar/toolbar area. 11951 */ 11952 fullSizeContentView = 1 << 15, 11953 11954 /* The following are only applicable for NSPanel (or a subclass thereof) 11955 */ 11956 utilityWindow = 1 << 4, 11957 docModalWindow = 1 << 6, 11958 nonactivatingPanel = 1 << 7, // Specifies that a panel that does not activate the owning application 11959 hUDWindow = 1 << 13 // Specifies a heads up display panel 11960 } 11961 11962 version(OSX) 11963 extern class NSWindow : NSObject { 11964 override static NSWindow alloc() @selector("alloc"); 11965 11966 override NSWindow init() @selector("init"); 11967 11968 NSWindow initWithContentRect( 11969 NSRect contentRect, 11970 NSWindowStyleMask style, 11971 NSBackingStoreType bufferingType, 11972 bool flag 11973 ) @selector("initWithContentRect:styleMask:backing:defer:"); 11974 11975 void makeKeyAndOrderFront(NSid sender) @selector("makeKeyAndOrderFront:"); 11976 NSView contentView() @selector("contentView"); 11977 void contentView(NSView view) @selector("setContentView:"); 11978 void orderFrontRegardless() @selector("orderFrontRegardless"); 11979 void center() @selector("center"); 11980 11981 NSRect frame() @selector("frame"); 11982 11983 NSRect contentRectForFrameRect(NSRect frameRect) @selector("contentRectForFrameRect:"); 11984 NSRect frameRectForContentRect(NSRect contentRect) @selector("frameRectForContentRect:"); 11985 11986 NSString title() @selector("title"); 11987 void title(NSString value) @selector("setTitle:"); 11988 11989 void close() @selector("close"); 11990 11991 NSWindowDelegate delegate_() @selector("delegate"); 11992 void delegate_(NSWindowDelegate) @selector("setDelegate:"); 11993 11994 void setBackgroundColor(NSColor color) @selector("setBackgroundColor:"); 11995 11996 void setIsVisible(bool b) @selector("setIsVisible:"); 11997 } 11998 11999 version(OSX) 12000 extern interface NSWindowDelegate { 12001 @optional: 12002 void windowDidResize(NSNotification notification) @selector("windowDidResize:"); 12003 12004 NSSize windowWillResize(NSWindow sender, NSSize frameSize) @selector("windowWillResize:toSize:"); 12005 12006 void windowWillClose(NSNotification notification) @selector("windowWillClose:"); 12007 } 12008 12009 version(OSX) 12010 extern class NSView : NSResponder { 12011 //override NSView init() @selector("init"); 12012 NSView initWithFrame(NSRect frameRect) @selector("initWithFrame:"); 12013 12014 void addSubview(NSView view) @selector("addSubview:"); 12015 12016 bool wantsLayer() @selector("wantsLayer"); 12017 void wantsLayer(bool value) @selector("setWantsLayer:"); 12018 12019 CALayer layer() @selector("layer"); 12020 void uiDelegate(NSObject) @selector("setUIDelegate:"); 12021 12022 void drawRect(NSRect rect) @selector("drawRect:"); 12023 bool isFlipped() @selector("isFlipped"); 12024 bool acceptsFirstResponder() @selector("acceptsFirstResponder"); 12025 bool setNeedsDisplay(bool) @selector("setNeedsDisplay:"); 12026 12027 // DO NOT USE: https://issues.dlang.org/show_bug.cgi?id=19017 12028 // an asm { pop RAX; } after getting the struct can kinda hack around this but still 12029 @property NSRect frame() @selector("frame"); 12030 @property NSRect frame(NSRect rect) @selector("setFrame:"); 12031 12032 void setFrameSize(NSSize newSize) @selector("setFrameSize:"); 12033 void setFrameOrigin(NSPoint newOrigin) @selector("setFrameOrigin:"); 12034 12035 void addSubview(NSView what) @selector("addSubview:"); 12036 void removeFromSuperview() @selector("removeFromSuperview"); 12037 } 12038 12039 extern class NSFont : NSObject { 12040 void set() @selector("set"); // sets it into the current graphics context 12041 void setInContext(NSGraphicsContext context) @selector("setInContext:"); 12042 12043 static NSFont fontWithName(NSString fontName, CGFloat fontSize) @selector("fontWithName:size:"); 12044 // fontWithDescriptor too 12045 // fontWithName and matrix too 12046 static NSFont systemFontOfSize(CGFloat fontSize) @selector("systemFontOfSize:"); 12047 // among others 12048 12049 @property CGFloat pointSize() @selector("pointSize"); 12050 @property bool isFixedPitch() @selector("isFixedPitch"); 12051 // fontDescriptor 12052 @property NSString displayName() @selector("displayName"); 12053 12054 @property CGFloat ascender() @selector("ascender"); 12055 @property CGFloat descender() @selector("descender"); // note it is negative 12056 @property CGFloat capHeight() @selector("capHeight"); 12057 @property CGFloat leading() @selector("leading"); 12058 @property CGFloat xHeight() @selector("xHeight"); 12059 // among many more 12060 } 12061 12062 extern class NSColor : NSObject { 12063 override static NSColor alloc() @selector("alloc"); 12064 static NSColor redColor() @selector("redColor"); 12065 static NSColor whiteColor() @selector("whiteColor"); 12066 12067 CGColorRef CGColor() @selector("CGColor"); 12068 } 12069 12070 extern class CALayer : NSObject { 12071 CGFloat borderWidth() @selector("borderWidth"); 12072 void borderWidth(CGFloat value) @selector("setBorderWidth:"); 12073 12074 CGColorRef borderColor() @selector("borderColor"); 12075 void borderColor(CGColorRef) @selector("setBorderColor:"); 12076 } 12077 12078 12079 version(OSX) 12080 extern class NSViewController : NSObject { 12081 NSView view() @selector("view"); 12082 void view(NSView view) @selector("setView:"); 12083 } 12084 12085 enum NSBackingStoreType : size_t { 12086 retained = 0, 12087 nonretained = 1, 12088 buffered = 2 12089 } 12090 12091 enum NSStringEncoding : NSUInteger { 12092 NSASCIIStringEncoding = 1, /* 0..127 only */ 12093 NSUTF8StringEncoding = 4, 12094 NSUnicodeStringEncoding = 10, 12095 12096 NSUTF16StringEncoding = NSUnicodeStringEncoding, 12097 NSUTF16BigEndianStringEncoding = 0x90000100, 12098 NSUTF16LittleEndianStringEncoding = 0x94000100, 12099 NSUTF32StringEncoding = 0x8c000100, 12100 NSUTF32BigEndianStringEncoding = 0x98000100, 12101 NSUTF32LittleEndianStringEncoding = 0x9c000100 12102 } 12103 12104 12105 struct CGColor; 12106 alias CGColorRef = CGColor*; 12107 12108 // note on the watch os it is float, not double 12109 alias CGFloat = double; 12110 12111 struct NSPoint { 12112 CGFloat x; 12113 CGFloat y; 12114 } 12115 12116 struct NSSize { 12117 CGFloat width; 12118 CGFloat height; 12119 } 12120 12121 struct NSRect { 12122 NSPoint origin; 12123 NSSize size; 12124 } 12125 12126 alias NSPoint CGPoint; 12127 alias NSSize CGSize; 12128 alias NSRect CGRect; 12129 12130 pragma(inline, true) NSPoint NSMakePoint(CGFloat x, CGFloat y) { 12131 NSPoint p; 12132 p.x = x; 12133 p.y = y; 12134 return p; 12135 } 12136 12137 pragma(inline, true) NSSize NSMakeSize(CGFloat w, CGFloat h) { 12138 NSSize s; 12139 s.width = w; 12140 s.height = h; 12141 return s; 12142 } 12143 12144 pragma(inline, true) NSRect NSMakeRect(CGFloat x, CGFloat y, CGFloat w, CGFloat h) { 12145 NSRect r; 12146 r.origin.x = x; 12147 r.origin.y = y; 12148 r.size.width = w; 12149 r.size.height = h; 12150 return r; 12151 } 12152 12153 12154 } 12155 12156 // helper raii refcount object 12157 static if(UseCocoa) 12158 struct MacString { 12159 union { 12160 // must be wrapped cuz of bug in dmd 12161 // referencing an init symbol when it should 12162 // just be null. but the union makes it work 12163 NSString s; 12164 } 12165 12166 // FIXME: if a string literal it would be kinda nice to use 12167 // the other function. but meh 12168 12169 this(scope const char[] str) { 12170 this.s = NSString.alloc.initWithBytes( 12171 cast(const(ubyte)*) str.ptr, 12172 str.length, 12173 NSStringEncoding.NSUTF8StringEncoding 12174 ); 12175 } 12176 12177 NSString borrow() { 12178 return s; 12179 } 12180 12181 this(this) { 12182 if(s !is null) 12183 s.retain(); 12184 } 12185 12186 ~this() { 12187 if(s !is null) { 12188 s.release(); 12189 s = null; 12190 } 12191 } 12192 } 12193 12194 extern(C) void NSLog(NSString, ...); 12195 extern(C) SEL sel_registerName(const(char)* str); 12196 12197 version(OSX) 12198 extern (Objective-C) __gshared NSApplication NSApp_; 12199 12200 version(OSX) 12201 NSApplication NSApp() { 12202 if(NSApp_ is null) 12203 NSApp_ = NSApplication.shared_; 12204 return NSApp_; 12205 } 12206 12207 version(DigitalMars) { 12208 // hacks to work around compiler bug 12209 extern(C) __gshared void* _D4arsd4core17NSGraphicsContext7__ClassZ = null; 12210 extern(C) __gshared void* _D4arsd4core6NSView7__ClassZ = null; 12211 extern(C) __gshared void* _D4arsd4core8NSWindow7__ClassZ = null; 12212 } 12213 12214 12215 12216 extern(C) { // grand central dispatch bindings 12217 12218 // /Library/Developer/CommandLineTools/SDKs/MacOSX13.1.sdk/usr/include/dispatch 12219 // https://swiftlang.github.io/swift-corelibs-libdispatch/tutorial/ 12220 // https://man.freebsd.org/cgi/man.cgi?query=dispatch_main&sektion=3&apropos=0&manpath=macOS+14.3.1 12221 12222 struct dispatch_source_type_s {} 12223 private __gshared immutable extern { 12224 dispatch_source_type_s _dispatch_source_type_timer; 12225 dispatch_source_type_s _dispatch_source_type_proc; 12226 dispatch_source_type_s _dispatch_source_type_signal; 12227 dispatch_source_type_s _dispatch_source_type_read; 12228 dispatch_source_type_s _dispatch_source_type_write; 12229 dispatch_source_type_s _dispatch_source_type_vnode; 12230 // also memory pressure and some others 12231 } 12232 12233 immutable DISPATCH_SOURCE_TYPE_TIMER = &_dispatch_source_type_timer; 12234 immutable DISPATCH_SOURCE_TYPE_PROC = &_dispatch_source_type_proc; 12235 immutable DISPATCH_SOURCE_TYPE_SIGNAL = &_dispatch_source_type_signal; 12236 immutable DISPATCH_SOURCE_TYPE_READ = &_dispatch_source_type_read; 12237 immutable DISPATCH_SOURCE_TYPE_WRITE = &_dispatch_source_type_write; 12238 immutable DISPATCH_SOURCE_TYPE_VNODE = &_dispatch_source_type_vnode; 12239 // also are some for internal data change things and a couple others 12240 12241 enum DISPATCH_PROC_EXIT = 0x80000000; // process exited 12242 enum DISPATCH_PROC_FORK = 0x40000000; // it forked 12243 enum DISPATCH_PROC_EXEC = 0x20000000; // it execed 12244 enum DISPATCH_PROC_SIGNAL = 0x08000000; // it received a signal 12245 12246 enum DISPATCH_VNODE_DELETE = 0x1; 12247 enum DISPATCH_VNODE_WRITE = 0x2; 12248 enum DISPATCH_VNODE_EXTEND = 0x4; 12249 enum DISPATCH_VNODE_ATTRIB = 0x8; 12250 enum DISPATCH_VNODE_LINK = 0x10; 12251 enum DISPATCH_VNODE_RENAME = 0x20; 12252 enum DISPATCH_VNODE_REVOKE = 0x40; 12253 enum DISPATCH_VNODE_FUNLOCK = 0x100; 12254 12255 private struct dispatch_source_s; 12256 private struct dispatch_queue_s {} 12257 12258 alias dispatch_source_type_t = const(dispatch_source_type_s)*; 12259 12260 alias dispatch_source_t = dispatch_source_s*; // NSObject<OS_dispatch_source> 12261 alias dispatch_queue_t = dispatch_queue_s*; // NSObject<OS_dispatch_queue> 12262 alias dispatch_object_t = void*; // actually a "transparent union" of the dispatch_source_t, dispatch_queue_t, and others 12263 alias dispatch_block_t = ObjCBlock!(void)*; 12264 static if(typeof(null).sizeof == 8) 12265 alias uintptr_t = ulong; 12266 else 12267 alias uintptr_t = uint; 12268 12269 dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, c_ulong mask, dispatch_queue_t queue); 12270 void dispatch_source_set_event_handler(dispatch_source_t source, dispatch_block_t handler); 12271 void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t handler); 12272 void dispatch_source_cancel(dispatch_source_t source); 12273 12274 // DISPATCH_DECL_SUBCLASS(dispatch_queue_main, dispatch_queue_serial); 12275 // dispatch_queue_t dispatch_get_main_queue(); 12276 12277 extern __gshared dispatch_queue_s _dispatch_main_q; 12278 12279 extern(D) dispatch_queue_t dispatch_get_main_queue() { 12280 return &_dispatch_main_q; 12281 } 12282 12283 // FIXME: what is dispatch_time_t ??? 12284 // dispatch_time 12285 // dispatch_walltime 12286 12287 // void dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, ulong interval, ulong leeway); 12288 12289 void dispatch_retain(dispatch_object_t object); 12290 void dispatch_release(dispatch_object_t object); 12291 12292 void dispatch_resume(dispatch_object_t object); 12293 void dispatch_pause(dispatch_object_t object); 12294 12295 void* dispatch_get_context(dispatch_object_t object); 12296 void dispatch_set_context(dispatch_object_t object, void* context); 12297 12298 // sends a function to the given queue 12299 void dispatch_sync(dispatch_queue_t queue, scope dispatch_block_t block); 12300 void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); 12301 12302 } // grand central dispatch bindings 12303 12304 }