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