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