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