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