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