The OpenD Programming Language

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 }