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