1 module eventcore.internal.utils;
2 
3 import core.memory : GC;
4 import std.traits : hasIndirections;
5 import taggedalgebraic;
6 
7 
8 void print(ARGS...)(string str, ARGS args)
9 @trusted @nogc nothrow {
10 	import std.format : formattedWrite;
11 	StdoutRange r;
12 	scope cb = () {
13 		try (&r).formattedWrite(str, args);
14 		catch (Exception e) assert(false, e.msg);
15 	};
16 	(cast(void delegate() @nogc @safe nothrow)cb)();
17 	r.put('\n');
18 }
19 
20 T mallocT(T, ARGS...)(ARGS args)
21 {
22 	import core.stdc.stdlib : malloc;
23 	import std.conv : emplace;
24 
25     T ret = () @trusted {
26         enum size = __traits(classInstanceSize, T);
27         T r = cast(T)malloc(size);
28         static if (hasIndirections!T)
29             GC.addRange(cast(void*)r, size);
30         return r;
31     }();
32 	return emplace!T(ret, args);
33 }
34 
35 void freeT(T)(ref T inst) @nogc
36 	if (is(T == class))
37 {
38 	import core.stdc.stdlib : free;
39 
40 	if (!inst) return;
41 
42 	noGCDestroy(inst);
43 	static if (hasIndirections!T)
44 		GC.removeRange(cast(void*)inst);
45 	free(cast(void*)inst);
46 	inst = null;
47 }
48 
49 T[] mallocNT(T)(size_t cnt)
50 @trusted {
51 	import core.stdc.stdlib : malloc;
52 	import std.conv : emplace;
53 
54 	auto ret = (cast(T*)malloc(T.sizeof * cnt))[0 .. cnt];
55 	static if (hasIndirections!T)
56 		GC.addRange(cast(void*)ret, T.sizeof * cnt);
57 	foreach (ref v; ret)
58 		static if (!is(T == class))
59 			emplace!T(&v);
60 		else v = null;
61 	return ret;
62 }
63 
64 void freeNT(T)(ref T[] arr)
65 {
66 	import core.stdc.stdlib : free;
67 
68 	foreach (ref v; arr)
69 		static if (!is(T == class))
70 			destroy(v);
71 	static if (hasIndirections!T)
72 		GC.removeRange(arr.ptr);
73 	free(arr.ptr);
74 	arr = null;
75 }
76 
77 private void noGCDestroy(T)(ref T t)
78 @trusted {
79 	// FIXME: only do this if the destructor chain is actually nogc
80 	scope doit = { destroy(t); };
81 	(cast(void delegate() @nogc)doit)();
82 }
83 
84 private extern(C) Throwable.TraceInfo _d_traceContext(void* ptr = null);
85 
86 void nogc_assert(bool cond, string message, string file = __FILE__, int line = __LINE__)
87 @trusted nothrow @nogc {
88 	import core.stdc.stdlib : abort;
89 	import std.stdio : stderr;
90 
91 	if (!cond) {
92 		scope (exit) {
93 			abort();
94 			assert(false);
95 		}
96 
97 		scope doit = {
98 			stderr.writefln("Assertion failure @%s(%s): %s", file, line, message);
99 			stderr.writeln("------------------------");
100 			if (auto info = _d_traceContext(null)) {
101 				foreach (s; info)
102 					stderr.writeln(s);
103 			} else stderr.writeln("no stack trace available");
104 		};
105 		(cast(void delegate() @nogc)doit)(); // write and _d_traceContext are not nogc
106 	}
107 }
108 
109 struct StdoutRange {
110 	@safe: @nogc: nothrow:
111 	import core.stdc.stdio;
112 
113 	void put(string str)
114 	{
115 		() @trusted { fwrite(str.ptr, str.length, 1, stderr); } ();
116 	}
117 
118 	void put(char ch)
119 	{
120 		() @trusted { fputc(ch, stderr); } ();
121 	}
122 }
123 
124 struct ChoppedVector(T, size_t CHUNK_SIZE = 16*64*1024/nextPOT(T.sizeof)) {
125 	static assert(nextPOT(CHUNK_SIZE) == CHUNK_SIZE,
126 		"CHUNK_SIZE must be a power of two for performance reasons.");
127 
128 	@safe: nothrow:
129 	import core.stdc.stdlib : calloc, free, malloc, realloc;
130 
131 	alias chunkSize = CHUNK_SIZE;
132 
133 	private {
134 		alias Chunk = T[chunkSize];
135 		alias ChunkPtr = Chunk*;
136 		ChunkPtr[] m_chunks;
137 		size_t m_chunkCount;
138 		size_t m_length;
139 	}
140 
141 	@disable this(this);
142 
143 	~this()
144 	@nogc {
145 		clear();
146 	}
147 
148 	@property size_t length() const @nogc { return m_length; }
149 
150 	void clear()
151 	@nogc {
152 		() @trusted {
153 			foreach (i; 0 .. m_chunkCount) {
154 				destroy(*m_chunks[i]);
155 				static if (hasIndirections!T)
156 					GC.removeRange(m_chunks[i]);
157 				free(m_chunks[i]);
158 			}
159 			free(m_chunks.ptr);
160 			m_chunks = null;
161 		} ();
162 		m_chunkCount = 0;
163 		m_length = 0;
164 	}
165 
166 	ref T opIndex(size_t index)
167 	@nogc {
168 		auto chunk = index / chunkSize;
169 		auto subidx = index % chunkSize;
170 		if (index >= m_length) m_length = index+1;
171 		reserveChunk(chunk);
172 		return (*m_chunks[chunk])[subidx];
173 	}
174 
175 	ref const(T) opIndex(size_t index)
176 	const @nogc {
177 		static immutable T emptySlot;
178 
179 		auto chunk = index / chunkSize;
180 		auto subidx = index % chunkSize;
181 		if (index >= m_length) return emptySlot;
182 		auto c = m_chunks[chunk];
183 		if (!c) return emptySlot;
184 		return (*c)[subidx];
185 	}
186 
187 	int opApply(scope int delegate(size_t idx, ref T) @safe nothrow del)
188 	{
189 		size_t idx = 0;
190 		outer: 
191 		foreach (c; m_chunks) {
192 			if (c) {
193 				foreach (i, ref t; *c) {
194 					if (auto ret = del(idx+i, t))
195 						return ret;
196 					if (i + idx >= length)
197 						break outer;
198 				}
199 			}
200 			idx += chunkSize;
201 		}
202 		return 0;
203 	}
204 
205 	int opApply(scope int delegate(size_t idx, ref const(T)) @safe nothrow del)
206 	const {
207 		size_t idx = 0;
208 		outer: 
209 		foreach (c; m_chunks) {
210 			if (c) {
211 				foreach (i, ref t; *c) {
212 					if (auto ret = del(idx+i, t))
213 						return ret;
214 					if (i + idx >= length)
215 						break outer;
216 				}
217 			}
218 			idx += chunkSize;
219 		}
220 		return 0;
221 	}
222 
223 	private void reserveChunk(size_t chunkidx)
224 	@nogc {
225 		if (m_chunks.length <= chunkidx) {
226 			auto l = m_chunks.length == 0 ? 64 : m_chunks.length;
227 			while (l <= chunkidx) l *= 2;
228 			() @trusted {
229 				auto newptr = cast(ChunkPtr*)realloc(m_chunks.ptr, l * ChunkPtr.length);
230 				assert(newptr !is null, "Failed to allocate chunk index!");
231 				newptr[m_chunks.length .. l] = ChunkPtr.init;
232 				m_chunks = newptr[0 .. l];
233 			} ();
234 		}
235 
236 		while (m_chunkCount <= chunkidx) {
237 			() @trusted {
238 				auto ptr = cast(ChunkPtr)calloc(chunkSize, T.sizeof);
239 				assert(ptr !is null, "Failed to allocate chunk!");
240 				// FIXME: initialize with T.init instead of 0
241 				static if (hasIndirections!T)
242 					GC.addRange(ptr, chunkSize * T.sizeof);
243 				m_chunks[m_chunkCount++] = ptr;
244 			} ();
245 		}
246 	}
247 }
248 
249 struct AlgebraicChoppedVector(TCommon, TSpecific...)
250 {
251 	import std.conv : to;
252 	import std.meta : AliasSeq;
253 
254 	union U {
255 		typeof(null) none;
256 		mixin fields!0;
257 	}
258 	alias FieldType = TaggedAlgebraic!U;
259 	static struct FullField {
260 		TCommon common;
261 		FieldType specific;
262 		mixin(accessors());
263 	}
264 
265 	ChoppedVector!(FullField) items;
266 
267 	alias items this;
268 
269 	private static string accessors()
270 	{
271 		import std.format : format;
272 		string ret;
273 		foreach (i, U; TSpecific)
274 			ret ~= "@property ref TSpecific[%s] %s() nothrow @safe @nogc { return this.specific.get!(TSpecific[%s]); }\n"
275 				.format(i, U.Handle.name, i);
276 		return ret;
277 	}
278 
279 	private mixin template fields(size_t i) {
280 		static if (i < TSpecific.length) {
281 			mixin("TSpecific["~i.to!string~"] "~TSpecific[i].Handle.name~";");
282 			mixin fields!(i+1);
283 		}
284 	}
285 }
286 
287 
288 
289 /** Efficient bit set of dynamic size.
290 */
291 struct SmallIntegerSet(V : size_t)
292 {
293 	private {
294 		uint[][4] m_bits;
295 		size_t m_count;
296 	}
297 
298 	@disable this(this);
299 
300 	@property bool empty() const { return m_count == 0; }
301 
302 	void insert(V i)
303 	{
304 		assert(i >= 0);
305 		foreach (j; 0 .. m_bits.length) {
306 			uint b = 1u << (i%32);
307 			i /= 32;
308 			if (i >= m_bits[j].length)
309 				m_bits[j].length = nextPOT(i+1);
310 			if (j == 0 && !(m_bits[j][i] & b)) m_count++;
311 			m_bits[j][i] |= b;
312 		}
313 	}
314 
315 	void remove(V i)
316 	{
317 		assert(i >= 0);
318 		if (i >= m_bits[0].length * 32) return;
319 
320 		foreach (j; 0 .. m_bits.length) {
321 			uint b = 1u << (i%32);
322 			i /= 32;
323 			if (!m_bits[j][i]) break;
324 			if (j == 0 && m_bits[j][i] & b) m_count--;
325 			m_bits[j][i] &= ~b;
326 			if (m_bits[j][i]) break;
327 		}
328 	}
329 
330 	bool contains(V i) const { return i/32 < m_bits[0].length && m_bits[0][i/32] & (1u<<(i%32)); }
331 
332 	int opApply(scope int delegate(V) @safe nothrow del)
333 	const @safe {
334 		int rec(size_t depth, uint bi)
335 		{
336 			auto b = m_bits[depth][bi];
337 			foreach (i; 0 .. 32)
338 				if (b & (1u << i)) {
339 					uint sbi = bi*32 + i;
340 					if (depth == 0) {
341 						if (auto ret = del(V(sbi)))
342 							return ret;
343 					} else rec(depth-1, sbi);
344 				}
345 			return 0;
346 		}
347 
348 		foreach (i, b; m_bits[$-1])
349 			if (b) {
350 				if (auto ret = rec(m_bits.length-1, cast(uint)i))
351 					return ret;
352 			}
353 
354 		return 0;
355 	}
356 }
357 
358 unittest {
359 	uint[] ints = [0, 16, 31, 128, 4096, 65536];
360 
361 	SmallIntegerSet!uint set;
362 	bool[uint] controlset;
363 
364 	assert(set.empty);
365 	foreach (i; ints) {
366 		set.insert(i);
367 		controlset[i] = true;
368 	}
369 	assert(!set.empty);
370 
371 	foreach (jidx, j; ints) {
372 		size_t cnt = 0;
373 		bool[int] seen;
374 		foreach (i; set) {
375 			assert(i in controlset);
376 			assert(i !in seen);
377 			seen[i] = true;
378 			cnt++;
379 		}
380 		assert(cnt == ints.length - jidx);
381 
382 		set.remove(j);
383 		controlset.remove(j);
384 	}
385 	assert(set.empty);
386 
387 	foreach (i; set) assert(false);
388 }
389 
390 @safe nothrow unittest {
391 	SmallIntegerSet!uint s;
392 
393 	void testIter(scope uint[] seq...) nothrow {
394 		size_t cnt = 0;
395 		foreach (v; s) {
396 			assert(v == seq[cnt]);
397 			cnt++;
398 		}
399 		assert(cnt == seq.length);
400 	}
401 
402 	testIter();
403 	s.insert(1);
404 	assert(s.contains(1));
405 	assert(!s.contains(2));
406 	testIter(1);
407 	s.insert(3467);
408 	assert(s.contains(3467));
409 	assert(!s.contains(300));
410 	testIter(1, 3467);
411 	s.insert(2);
412 	testIter(1, 2, 3467);
413 	s.remove(1);
414 	testIter(2, 3467);
415 	s.remove(2);
416 	testIter(3467);
417 	s.remove(3467);
418 	testIter();
419 }
420 
421 private size_t nextPOT(size_t n) @safe nothrow @nogc
422 {
423 	foreach_reverse (i; 0 .. size_t.sizeof*8) {
424 		size_t ni = cast(size_t)1 << i;
425 		if (n & ni) {
426 			return n & (ni-1) ? ni << 1 : ni;
427 		}
428 	}
429 	return 1;
430 }
431 
432 unittest {
433 	assert(nextPOT(1) == 1);
434 	assert(nextPOT(2) == 2);
435 	assert(nextPOT(3) == 4);
436 	assert(nextPOT(4) == 4);
437 	assert(nextPOT(5) == 8);
438 }