The OpenD Programming Language

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