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