module dlib.util; import dlib.aliases; import dlib.alloc; import std.traits : isIntegral, hasMember, isArray; import core.stdc.string; //import std.traits; static if(NativeTarget) { import std.format : sformat; import std.stdio : write, writeln, writef, writefln, stderr; import dlib.platform; } enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[]) || is(T: const(char)[])); pragma(inline) void Int3() { static if(NativeTarget) { debug asm { db 0xCC; } } } pragma(inline) void Pause() { static if(NativeTarget) { asm { rep; nop; } } } void Assert(T)(T cond, string msg) { if(!cond) { assert(false, msg); } } string Str(T)(T arr) if(StringType!(T)) { return (cast(immutable(char)*)arr.ptr)[0 .. arr.length]; } alias ConvToStr = Str; T[] Arr(T)(string str) { T[] arr = (cast(T*)str.ptr)[0 .. str.length]; return arr; } alias CastStr = Arr; T[] CastArr(T, U)(U[] input_array) { static assert(T.sizeof == U.sizeof); T[] output_array = (cast(T*)input_array.ptr)[0 .. input_array.length]; return output_array; } void Logf(string prefix = "INFO ", Args...)(string fmt, Args args) { static if(NativeTarget) { try { writef("[%s]: ", prefix); writefln(fmt, args); } catch (Exception e) { assert(false, "Incompatible format type"); } } } void Logif(Args...)(string fmt, Args args, string func = __FUNCTION__) { static if(NativeTarget) { try { writef("FN: [%s] ", func); Logf(fmt, args); } catch (Exception e) { assert(false, "Incompatible format type"); } } } void Warnf(Args...)(string fmt, Args args) { Logf!("WARN ", Args)(fmt, args); } void Errf(Args...)(string fmt, Args args) { Logf!("ERROR", Args)(fmt, args); } void Debugf(Args...)(string fmt, Args args) { debug Logf(fmt, args, "[DEBUG]: "); } string Scratchf(Args...)(string fmt, Args args) { static if(NativeTarget) { assert(g_scratch.init); char[] buf = ScratchAlloc!(char)(fmt.length < 16 ? 32 : 128); return Str(sformat(buf, fmt, args)); } else static assert(false, NO_IMPL); } void Log(string str) { static if(NativeTarget) writeln(str); } void Log(char* str) { static if(NativeTarget) writeln(str); } @nogc usize KB(usize v) { return v * 1024; }; @nogc usize MB(usize v) { return KB(v) * 1024; }; @nogc usize GB(usize v) { return MB(v) * 1024; }; u32 StrCharCount(T)(T str, u8 ch) if(is(T: string) || is(T: u8[]) || is(T: char[])) { u32 count = 0; for(u64 i = 0; i < str.length; i += 1) { count += cast(u8)str[i] == ch; } return count; } pragma(inline) void ConvertColor(Vec4 *dst, u32 src) { if(src == 0) { dst.rgb = 0.0; dst.a = 1.0; } else { Convert(dst, src); } } string GetFilePath(string file_name) { string result = file_name; for(usize i = file_name.length-1; i64(i) >= 0; i -= 1) { version(Windows) { char ch = '\\'; } else { char ch = '/'; } if(file_name[i] == ch) { result = file_name[0 .. i+1]; break; } } return result; } pragma(inline) void Convert(Vec4* dst, u32 src) { dst.r = cast(f32)((src >> 0) & 0xFF) / 255.0; dst.g = cast(f32)((src >> 8) & 0xFF) / 255.0; dst.b = cast(f32)((src >> 16) & 0xFF) / 255.0; dst.a = cast(f32)((src >> 24) & 0xFF) / 255.0; } bool BitEq(u64 l, u64 r) { return (l & r) == r; } struct ListBox(T, bool doubly_linked) { alias U = ListBox!(T, doubly_linked); U* next; static if(doubly_linked) { U* prev; } T value; } struct LinkedList(T) { T* first, last; } void ConcatInPlace(T)(T* list, T* to_concat) { if(to_concat.first) { if(list.first) { list.last.next = to_concat.first; list.last = to_concat.last; } else { list.first = to_concat.first; list.last = to_concat.last; } memset(to_concat, 0, T.sizeof); } } U* DLLPop(T, U)(T* list, U* nil) { U* node = list.first; if (!CheckNil(nil, list.first)) { if(list.first == list.last) { list.first = list.last = nil; } else { list.first = list.first.next; list.first.prev = nil; } } return node; } void DLLRemove(T, U)(T* list, U* node, U* nil) { if(list.first == list.last) { list.first = list.last = nil; } else if(list.first == node) { list.first = node.next; list.first.prev = nil; } else if(list.last == node) { node.prev.next = nil; list.last = node.prev; } else { node.next.prev = node.prev; node.prev.next = node.next; } } void DLLPushFront(T, U)(T* list, U* node, U* nil) { node.prev = node.next = nil; if(CheckNil(nil, list.first)) { list.first = list.last = node; } else { node.next = list.first; node.prev = nil; list.first.prev = node; list.first = node; } } void DLLPush(T, U)(T* list, U* node, U* nil) { node.prev = node.next = nil; if(CheckNil(nil, list.first)) { list.first = list.last = node; } else { list.last.next = node; node.prev = list.last; list.last = node; node.next = nil; } } void DLLInsert(T, U)(T* list, U* node, U* prev, U* nil) if(hasMember!(T, "last") && hasMember!(U, "prev")) { if(CheckNil(nil, list.first) && CheckNil(nil, list.last)) { list.first = list.last = node; node.next = node.prev = nil; } else if(!CheckNil(nil, prev)) { node.next = list.first; list.first.prev = node; list.first = node; node.prev = nil; } else if(list.last == prev) { list.last.next = node; node.prev = list.last; list.last = node; node.next = nil; } else if(!CheckNil(nil, prev) && CheckNil(nil, prev.next)) {} else { node.prev = prev; node.next = prev.next; node.next.prev = node; prev.next = node; } } pragma(inline) bool CheckNil(T)(T* nil, T* node) { return node == null || node == nil; } pragma(inline) U* SLLPop(T, U)(T* list, U* nil) { U* node = list.first; if(list.first == list.last) { list.first = list.last = nil; } else { list.first = list.first.next; } return node; } pragma(inline) void SLLRemove(T, U)(T* list, U* node, U* prev, U* nil) { if(list.first == list.last) { list.first = list.last = nil; } else if(list.first == node) { list.first = node.next; } else if(list.last == node) { list.last = prev; prev.next = nil; } else { prev.next = node.next; } } pragma(inline) void SLLPushFront(T, U)(T* list, U* node, U* nil) { node.next = nil; if(CheckNil(nil, list.first)) { list.first = list.last = node; } else { node.next = list.first; list.first = node; } } pragma(inline) void SLLPush(T, U)(T* list, U* node, U* nil) { node.next = nil; if(CheckNil(nil, list.first)) { list.first = list.last = node; } else { list.last.next = node; list.last = node; node.next = nil; } } struct KVPair(K, V) { K key; V value; KVPair!(K, V)* next; } struct Result(V) { V value; bool ok; } struct HashTable(K, V) { alias P = KVPair!(K, V); LinkedList!(P) free_lists; LinkedList!(P)[] lists; P* nil; Arena arena; u64 node_count; u64 list_count; void opIndexAssign(V value, K key) { Push(&this, key, value); } Result!(V) opIndex(K key) { P* pair = Search(&this, key); Result!(V) result = { ok: false }; if(pair != null) { result.value = pair.value; result.ok = true; } return result; } } HashTable!(K, V) CreateHashTable(K, V)(usize size) { Arena arena = CreateArena(MB(4)); auto nil = Alloc!(KVPair!(K, V))(&arena); auto lists = Alloc!(LinkedList!(KVPair!(K, V)))(&arena, size); HashTable!(K, V) table = { arena: arena, lists: lists, list_count: size, nil: nil, free_lists: { first: nil, last: nil, }, }; foreach(list; table.lists) { list.first = nil; list.last = nil; } return table; } pragma(inline) void Clear(K, V)(HashTable!(K, V)* ht) { table.count = 0; foreach(i, list; ht.lists) { ConcatInPlace(&ht.free_lists, ht.lists.ptr + i); } } pragma(inline) KVPair!(K, V)* Push(K, V)(HashTable!(K, V)* ht, K key, V value) { alias P = KVPair!(K, V); P* node = ht.nil; if(!CheckNil(ht.nil, ht.free_lists.first)) { node = SLLPop(&ht.free_lists, ht.nil); } else { node = Alloc!(P)(&ht.arena); } node.next = ht.nil; node.key = key; node.value = value; SLLPush(GetList(ht, key), node, ht.nil); ht.node_count += 1; return node; } pragma(inline) KVPair!(K, V)* Search(K, V)(HashTable!(K, V)* ht, K key) { KVPair!(K, V)* result = null; auto list = GetList(ht, key); for(auto node = list.first; !CheckNil(ht.nil, node); node = node.next) { if(node.key == key) { result = node; break; } } return result; } pragma(inline) LinkedList!(KVPair!(K, V))* GetList(K, V)(HashTable!(K, V)* ht, K key) { u64 hash = Hash(&key); u64 index = hash % ht.list_count; return ht.lists.ptr + index; } KVPair!(K, V)*[] GetAllNodes(K, V)(Arena* arena, HashTable!(K, V)* ht) { KVPair!(K, V)*[] pairs = Alloc!(KVPair!(K, V)*)(arena, ht.node_count); if(ht.node_count > 0) { u64 count = 0; for(u64 i = 0; i < ht.lists.length; i += 1) { for(auto n = ht.lists[i].first; !CheckNil(ht.nil, n); n = n.next) { pairs[count] = n; count += 1; } } } return pairs; } pragma(inline) Result!(V) Delete(K, V)(HashTable!(K, V)* ht, K key) { Result!(V) result = { ok: false }; auto list = GetList(ht, key); auto prev = ht.nil; for(auto node = list.first; !CheckNil(ht.nil, node); prev = node, node = node.next) { if(node.key == key) { SLLRemove(list, node, prev, ht.nil); result.ok = true; result.value = node.value; memset(&node.value, 0, node.value.sizeof); SLLPush(&ht.free_lists, node, ht.nil); ht.node_count -= 1; break; } } return result; } const u64 HASH_SEED = 5995; pragma(inline) u64 Hash(T)(T[] value) { return XXHash64(value.ptr, (T.sizeof * value.length) / u8.sizeof, HASH_SEED); } pragma(inline) u64 Hash(T)(T* value) { return XXHash64(value, T.sizeof / u8.sizeof, HASH_SEED); } pragma(inline) u64 Hash(string str) { return XXHash64(str.ptr, str.length, HASH_SEED); } // TODO: probably needs improvement/testing struct IntervalTimer { Timer base; u64 interval; alias base this; } IntervalTimer CreateFPSTimer(u64 fps) { return CreateTimer(1000/fps); } IntervalTimer CreateTimer(u64 ms) { IntervalTimer timer = { base: CreateTimer() }; timer.interval = timer.cpu_freq * (ms/1000); return timer; } void ResetTimer(IntervalTimer* t) { static if(NativeTarget) { t.prev = RDTSC(); } else { assert(false, NO_IMPL); } } pragma(inline) bool CheckTimer(IntervalTimer* t) { bool result = false; static if(NativeTarget) { u64 time = RDTSC(); if(time - t.prev > t.interval) { result = true; t.prev = time; } } else { assert(false, NO_IMPL); } return result; } struct Timer { u64 cpu_freq; u64 prev; } Timer CreateTimer() { static if(NativeTarget) { u64 ms_to_wait = 50; u64 os_freq = OSTimeFreq(); u64 cpu_start = RDTSC(); u64 os_start = OSTime(); u64 os_end = 0; u64 os_elapsed = 0; u64 os_wait_time = os_freq * ms_to_wait / 1000; while (os_elapsed < os_wait_time) { os_end = OSTime(); os_elapsed = os_end - os_start; } u64 cpu_end = RDTSC(); u64 cpu_elapsed = cpu_end - cpu_start; u64 cpu_freq = 0; if(os_elapsed) { cpu_freq = os_freq * cpu_elapsed / os_elapsed; } Timer timer = { cpu_freq: cpu_freq, prev: RDTSC(), }; } else { Timer timer; assert(false, NO_IMPL); } return timer; } pragma(inline) f32 DeltaTime(Timer* t) { u64 time, step; static if(NativeTarget) { time = RDTSC(); step = time - t.prev; t.prev = time; } else { assert(false, NO_IMPL); } return cast(f32)(step) / cast(f32)(t.cpu_freq); } static string Replace(string target, u8 find, string replace) { assert(__ctfe, "Function can only be run at compile time"); u8[1024] result = '\0'; usize count; foreach(ch; target) { if(ch == find) { for(usize i = 0; i < replace.length; i += 1) { result[count++] = replace[i]; } } else { result[count++] = ch; } } return Str(result[0 .. count]); } static string Str(T)(T v) if(isIntegral!(T)) { assert(__ctfe); if(v == 0) { return "0"; } u8[] slice = new u8[32]; enum base = 10; i32 i = 30; for(; v && i; i--, v /= base) { slice[i] = r"0123456789"[v%base]; } return Str(slice[i+1 .. $]); } void MemSet(void* dst_p, i32 ch, u64 count) @nogc nothrow { u8[] slice = (cast(u8*)dst_p)[0 .. cast(usize)count]; slice[] = cast(u8)(ch&0xFF); } void MemCpy(void* dst_p, void* src_p, usize length) { u8* dst = cast(u8*)dst_p; u8* src = cast(u8*)src_p; usize remaining = length; version(X86_64) { if(remaining >= 64) { for(u64 i = 0; i + 64 < length; i += 64) { asm { mov R8, src; mov R9, dst; add R8, i; movdqu XMM0, [R8+00]; movdqu XMM1, [R8+16]; movdqu XMM2, [R8+32]; movdqu XMM3, [R8+48]; add R9, i; movups [R9+00], XMM0; movups [R9+16], XMM1; movups [R9+32], XMM2; movups [R9+48], XMM3; sub remaining, 64; } } } if(remaining >= 32) { for(u64 i = length - remaining; i + 32 < length; i += 32) { asm { mov R8, src; mov R9, dst; add R8, i; movdqu XMM0, [R8+00]; movdqu XMM1, [R8+16]; add R9, i; movdqu [R9+00], XMM0; movdqu [R9+16], XMM1; sub remaining, 32; } } } } if(remaining > 0) { dst[length-remaining .. length] = src[length-remaining .. length]; } } void Assign(T, Args...)(T slice, Args args) if(isArray!(T)) { assert(slice.length == Args.length); static foreach(i; 0 .. Args.length) { slice[i] = cast(typeof(slice[0]))args[i]; } } const u64 PRIME_1 = 11400714785074694791UL; const u64 PRIME_2 = 14029467366897019727UL; const u64 PRIME_3 = 1609587929392839161UL; const u64 PRIME_4 = 9650029242287828579UL; const u64 PRIME_5 = 2870177450012600261UL; const u64 HASH_MAX_BUFFER_SIZE = 31+1; struct XXHash { u64[4] state; u8[HASH_MAX_BUFFER_SIZE] buffer; usize buffer_size; usize total_length; } static XXHash XXHASH; u64 XXHash64(const(void)* input, u64 length, u64 seed) { InitHash(seed); HashAdd(input, length); return GetHash(); } void InitHash(u64 seed) { ref XXHash xxh = XXHASH; xxh.state[0] = seed + PRIME_1 + PRIME_2; xxh.state[1] = seed + PRIME_2; xxh.state[2] = seed; xxh.state[3] = seed - PRIME_1; xxh.buffer_size = 0; xxh.total_length = 0; } bool HashAdd(const(void)* input, u64 length) { if(!input || length == 0) return false; ref XXHash xxh = XXHASH; xxh.total_length += length; const(u8)* data = cast(const(u8)*)input; if(xxh.buffer_size+length < HASH_MAX_BUFFER_SIZE) { for(;length--;) { xxh.buffer[xxh.buffer_size++] = *(data++); } return true; } const(u8)* stop = data+length; const(u8)* stop_block = stop - HASH_MAX_BUFFER_SIZE; if(xxh.buffer_size > 0) { for(; xxh.buffer_size < HASH_MAX_BUFFER_SIZE;) { xxh.buffer[xxh.buffer_size++] = *data++; } ProcessHash(xxh.buffer.ptr, xxh.state[0], xxh.state[1], xxh.state[2], xxh.state[3]); } u64 s0 = xxh.state[0]; u64 s1 = xxh.state[1]; u64 s2 = xxh.state[2]; u64 s3 = xxh.state[3]; while(data <= stop_block) { ProcessHash(data, s0, s1, s2, s3); data += 32; } xxh.state[0] = s0; xxh.state[1] = s1; xxh.state[2] = s2; xxh.state[3] = s3; xxh.buffer_size = stop - data; for(u32 i = 0; i < xxh.buffer_size; i += 1) { xxh.buffer[i] = data[i]; } return true; } u64 GetHash() { ref XXHash xxh = XXHASH; u64 result; if(xxh.total_length >= HASH_MAX_BUFFER_SIZE) { result = RotateLeft(xxh.state[0], 1) + RotateLeft(xxh.state[1], 7) + RotateLeft(xxh.state[2], 12) + RotateLeft(xxh.state[3], 18); result = (result ^ ProcessSingleHashValue(0, xxh.state[0])) * PRIME_1 + PRIME_4; result = (result ^ ProcessSingleHashValue(0, xxh.state[1])) * PRIME_1 + PRIME_4; result = (result ^ ProcessSingleHashValue(0, xxh.state[2])) * PRIME_1 + PRIME_4; result = (result ^ ProcessSingleHashValue(0, xxh.state[3])) * PRIME_1 + PRIME_4; } else { result = xxh.state[2] + PRIME_5; } result += xxh.total_length; const(u8)* data = xxh.buffer.ptr; const(u8)* stop = data + xxh.buffer_size; for(; data + 8 <= stop; data += 8) { result = RotateLeft(result ^ ProcessSingleHashValue(0, *cast(u64*)data), 27) * PRIME_1 + PRIME_4; } if(data+4 <= stop) { result = RotateLeft(result ^ (*cast(u32*)data) * PRIME_1, 23) * PRIME_2 + PRIME_3; data += 4; } while(data != stop) { result = RotateLeft(result ^ (*data++) * PRIME_5, 11) * PRIME_1; } result ^= result >> 33; result *= PRIME_2; result ^= result >> 29; result *= PRIME_3; result ^= result >> 32; return result; } pragma(inline) u64 RotateLeft(u64 x, u8 bits) { return (x << bits) | (x >> (64 - bits)); } pragma(inline) u64 ProcessSingleHashValue(u64 previous, u64 input) { return RotateLeft(previous + input*PRIME_2, 31) * PRIME_1; } pragma(inline) void ProcessHash(const(void)* data, ref u64 state_0, ref u64 state_1, ref u64 state_2, ref u64 state_3) { u64* block = cast(u64*)data; state_0 = ProcessSingleHashValue(state_0, block[0]); state_1 = ProcessSingleHashValue(state_1, block[1]); state_2 = ProcessSingleHashValue(state_2, block[2]); state_3 = ProcessSingleHashValue(state_3, block[3]); } version(DLIB_TEST) unittest { { // Singly Linked List alias LT = ListBox!(u32, false); LinkedList!(LT) list; LT[5] nodes; foreach(u32 i, n; nodes) { nodes[i].value = i; SLLPush(&list, &nodes[i], null); } u32 count = 0; u32[3] res1 = [0, 2, 4]; SLLRemove(&list, &nodes[1], &nodes[0], null); SLLRemove(&list, &nodes[3], &nodes[2], null); LT* n = list.first; assert(list.first != null && list.last != null); assert(n != null); assert(n.next != null); void TestSLList(LinkedList!(LT)* list, u32[] result) { LT* n = list.first; foreach(i, v; result) { assert(n != null); assert(v == n.value); if(i == result.length-1) { assert(n.next == null); assert(n == list.last); } n = n.next; } } TestSLList(&list, res1); count = 0; u32[5] res2 = [3, 0, 2, 4, 1]; SLLPushFront(&list, &nodes[3], null); SLLPush(&list, &nodes[1], null); TestSLList(&list, res2); count = 0; SLLRemove(&list, &nodes[3], null, null); SLLRemove(&list, &nodes[1], &nodes[4], null); TestSLList(&list, res1); } { // Doubly Linked List alias LT = ListBox!(u32, true); void TestDLList(T)(T* list, u32[] result) { LT* n = list.first; foreach(i, v; result) { assert(n != null); assert(v == n.value); if(i > 0) { assert(n.prev != null); } if(i == result.length-1) { assert(n.next == null); assert(n == list.last); } n = n.next; } n = list.last; foreach_reverse(i, v; result) { assert(n != null); assert(v == n.value); if(i == result.length-1) { assert(n.next == null); } if(i == 0) { assert(n.prev == null); assert(n == list.first); } n = n.prev; } } LinkedList!(LT) list; LT[5] nodes; foreach(u32 i, n; nodes) { nodes[i].value = i; DLLPush(&list, &nodes[i], null); } assert(list.first != null && list.last != null); u32[5] test1 = [0, 1, 2, 3, 4]; TestDLList(&list, test1); u32 count = 0; u32[3] res1 = [0, 2, 4]; DLLRemove(&list, &nodes[1], null); DLLRemove(&list, &nodes[3], null); TestDLList(&list, res1); count = 0; u32[5] res2 = [3, 0, 2, 4, 1]; DLLPushFront(&list, &nodes[3], null); DLLPush(&list, &nodes[1], null); TestDLList(&list, res2); DLLRemove(&list, &nodes[3], null); DLLRemove(&list, &nodes[1], null); TestDLList(&list, res1); DLLInsert(&list, &nodes[1], &nodes[0], null); } { // MemCpy u8[777] bytes; u8[777] test_bytes; bytes[0 .. 123] = 123; bytes[123 .. 333] = 133; bytes[333 .. 655] = 155; bytes[655 .. $] = 199; test_bytes[0 .. $] = bytes[0 .. $]; assert(test_bytes == bytes); test_bytes[0 .. $] = 0; MemCpy(test_bytes.ptr, bytes.ptr, 777); assert(test_bytes == bytes); test_bytes[0 .. $] = 0; MemCpy(test_bytes.ptr+100, bytes.ptr, 32); u32 count = 0; foreach(i, v; test_bytes[100 .. 132]) { if(v != bytes[count]) { assert(false); } count += 1; } assert(test_bytes[100 .. 132] == bytes[0 .. 32]); test_bytes[0 .. $] = 0; MemCpy(test_bytes.ptr, bytes.ptr, 33); assert(test_bytes[0 .. 33] == bytes[0 .. 33]); test_bytes[0 .. $] = 0; MemCpy(test_bytes.ptr, bytes.ptr, 65); assert(test_bytes[0 .. 65] == bytes[0 .. 65]); test_bytes[0 .. $] = 0; MemCpy(test_bytes.ptr, bytes.ptr, 96); foreach(i, v; test_bytes[0 .. 96]) { if(v != bytes[i]) { assert(false); } } assert(test_bytes[0 .. 96] == bytes[0 .. 96]); } { // Hash Table auto table = CreateHashTable!(u64, u64)(10); table[100] = 100; } { // Hash u8[10] arr_1 = 5; u64[10] arr_2 = 555; u64 val_1 = 555555; u32 val_2 = 33333; u64 v1 = Hash(arr_1); u64 v2 = Hash(arr_2); u64 v3 = Hash(&val_1); assert(v1 > 0); assert(v2 > 0); assert(v3 > 0); } { // Casts u8[] arr = CastStr!(u8)("Test"); char[2] char_arr = ['a', 'b']; arr = CastArr!(u8)(char_arr); } static if(NativeTarget) { ResetScratch(MB(4)); string str = Scratchf("%s testing %s", 55, "testing"); assert(str == "55 testing testing"); } { u64[555] slice = 53321; MemSet(slice.ptr, 0, u64.sizeof*slice.length); for(usize i = 0; i < slice.length; i += 1) { assert(slice[i] == 0); } MemSet(slice.ptr, 5, u64.sizeof*slice.length); u64 cmp = cast(u64)( cast(u64)(5) << 56 | cast(u64)(5) << 48 | cast(u64)(5) << 40 | cast(u64)(5) << 32 | cast(u64)(5) << 24 | cast(u64)(5) << 16 | cast(u64)(5) << 8 | cast(u64)(5) << 0 ); for(usize i = 0; i < slice.length; i += 1) { assert(slice[i] == cmp); } } }