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