module dlib.util; import dlib.aliases; import dlib.alloc; import xxhash3; import includes; import std.stdio; import std.conv; import std.string; import core.stdc.string : memset; import core.simd; void Logf(Args...)(string fmt, Args args) { try { writefln(fmt, args); } catch (Exception e) { assert(false, "Incompatible format type"); } } void Log(string str) { writeln(str); } void Log(char* str) { writeln(str); } @nogc u64 KB(u64 v) { return v * 1024; }; @nogc u64 MB(u64 v) { return KB(v) * 1024; }; @nogc u64 GB(u64 v) { return MB(v) * 1024; }; pragma(inline) void ConvertColor(Vec4 *dst, u32 src) { if (src == 0) { dst.rgb = 0.0; dst.a = 1.0; } else { Convert(dst, src); } } 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 DNode(T) { DNode!(T)* next; DNode!(T)* prev; T value; } struct DLList(T) { DNode!(T)* first; DNode!(T)* 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* SLLPop(T, U)(T* list, T* nil) { U* node = list.first; if (list.first == list.last) { list.first = list.last = list.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; } node.prev = node.next = nil; } void DLLPushFront(T, U)(T* list, U* node, U* nil) { if (CheckNil(nil, list.first)) { list.first = list.last = node; node.prev = node.next = nil; } 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) { if (CheckNil(nil, list.first)) { list.first = list.last = node; node.prev = node.next = nil; } else { list.last.next = node; node.prev = list.last; list.last = node; node.next = nil; } } struct Stack(T) { Node!(T)* first; } void SPush(T, U)(T* stack, U* node, U* nil) { if (CheckNil(nil, stack.first)) { stack.first = node; node.next = nil; } else { node.next = stack.first; stack.first = node; } } U* SPop(T, U)(T* stack, U* nil) { U* node = stack.first; if (!CheckNil(nil, stack.first)) { stack.first = stack.first.next; } return node; } struct Node(T) { Node!(T)* next; T value; } struct SLList(T) { Node!(T)* first; Node!(T)* last; } 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; } node.next = nil; } pragma(inline) void SLLPushFront(T, U)(T* list, U* node, U* nil) { if (CheckNil(nil, list.first)) { list.first = list.last = node; node.next = nil; } else { node.next = list.first; list.first = node; } } pragma(inline) void SLLPush(T, U)(T* list, U* node, U* nil) { if (CheckNil(nil, list.first)) { list.first = list.last = node; node.next = nil; } else { list.last.next = node; list.last = node; node.next = nil; } } struct KVPair(K, V) { K key; V value; } struct Result(V) { V value; bool ok; } struct HashTable(K, V) { alias P = KVPair!(K, V); SLList!(P) free_lists; SLList!(P)[] lists; Node!(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); assert(pair != null, "HashTable key index failure: Result must be present"); Result!(V) result = { ok: false }; if (pair != null) { result.value = pair.value; result.ok = true; } return result; } } HashTable!(K, V) CreateHashTable(K, V)(u64 size) { Arena arena = CreateArena(MB(4)); auto nil = Alloc!(Node!(KVPair!(K, V)))(&arena); auto lists = AllocArray!(SLList!(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) Node!(KVPair!(K, V))* Push(K, V)(HashTable!(K, V)* ht, K key, V value) { alias P = KVPair!(K, V); alias N = Node!(P); N* node = ht.nil; if (CheckNil(ht.nil, ht.free_lists.first)) { node = SLLPop(&ht.free_lists, ht.nil); } else { node = Alloc!(N)(&ht.arena); } node.next = ht.nil; node.value.key = key; node.value.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.value.key == key) { result = &node.value; break; } } return result; } pragma(inline) SLList!(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; } 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); node = node.next) { if (node.value.key == key) { Remove(list, node, prev, ht.nil); result.ok = true; result.value = node.value.value; memset(&node.value, 0, node.value.sizeof); Push(&ht.free_lists, node, ht.nil); break; } } return result; } const u64 HASH_SEED = 5995; pragma(inline) u64 Hash(T)(T[] value) { return xxh3_64bits_withSeed(value.ptr, (T.sizeof * value.length) / u8.sizeof, HASH_SEED); } pragma(inline) u64 Hash(T)(T* value) { return xxh3_64bits_withSeed(value, T.sizeof / u8.sizeof, HASH_SEED); } pragma(inline) u64 Hash(string str) { return xxh3_64bits_withSeed(str.ptr, str.length, HASH_SEED); } pragma(inline) u64 RDTSC() { union u64_split { u64 full; struct { u32 lower; u32 upper; }; }; u64_split val; u64_split* valp = &val; asm { cpuid; rdtsc; mov R8, valp; mov valp.upper.offsetof[R8], EDX; mov valp.lower.offsetof[R8], EAX; } return val.full; } pragma(inline) u64 OSTimeFreq() { version (linux) { u64 freq = 1000000; } return freq; } pragma(inline) u64 OSTime() { version(linux) { import core.sys.linux.sys.time; timeval value; gettimeofday(&value, null); u64 time = OSTimeFreq() * cast(u64)(value.tv_sec) + cast(u64)(value.tv_usec); } return time; } // TODO: probably needs improvement/testing struct IntervalTimer { u64 cpu_freq; u64 interval; u64 prev; } IntervalTimer CreateTimer(u64 fps) { IntervalTimer timer; 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.cpu_freq = cpu_freq; timer.interval = cpu_freq/(fps+1); timer.prev = RDTSC(); return timer; } pragma(inline) bool CheckTimer(IntervalTimer* t) { bool result = false; u64 time = RDTSC(); if (time - t.prev > t.interval) { result = true; t.prev = time; } return result; } struct Timer { u64 cpu_freq; u64 prev; } Timer CreateTimer() { 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(), }; return timer; } pragma(inline) f32 DeltaTime(Timer* t) { u64 time = RDTSC(); u64 step = time - t.prev; t.prev = time; return cast(f32)(step) / cast(f32)(t.cpu_freq); } static string IntToStr(int n) nothrow pure @safe { string result; static immutable string[] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; if (n < table.length) { result = table[n]; } else { result = to!string(n); } return result; } static string GenerateLoop(string format_string, int N)() nothrow pure @safe { string result; for (int i = 0; i < N; i++) { result ~= format_string.replace("@", IntToStr(i)); } return result; } void MemCpy(void* dst_p, void* src_p, u64 length) { u8* dst = cast(u8*)dst_p; u8* src = cast(u8*)src_p; u64 remaining = length; 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; movdqu [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]; } } u8[] Embed(string file_name) { import std.file; return cast(u8[])read(file_name); } unittest { { // Singly Linked List SLList!(u32) list; Node!(u32)[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); Node!(u32)* n = list.first; assert(list.first != null && list.last != null); assert(n != null); assert(n.next != null); void TestSLList(SLList!(u32)* list, u32[] result) { Node!(u32)* 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 void TestDLList(DLList!(u32)* list, u32[] result) { DNode!(u32)* 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; } } DLList!(u32) list; DNode!(u32)[5] nodes; foreach(u32 i, n; nodes) { nodes[i].value = i; DLLPush(&list, &nodes[i], null); } assert(list.first != null && list.last != null); TestDLList(&list, [0, 1, 2, 3, 4]); 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); } { // MemCpy import std.conv; 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]) { Logf("Failed %d %d %d", i, 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; } { // Stack Stack!(u32) stack; Node!(u32) n1 = { value: 1 }; Node!(u32) n2 = { value: 2 }; Node!(u32) n3 = { value: 3 }; SPush(&stack, &n1, null); SPush(&stack, &n2, null); SPush(&stack, &n3, null); u32 count = 3; for (auto n = stack.first; !CheckNil(null, n); n = n.next, count -= 1) { assert(n.value == count); } count = 3; for (auto n = SPop(&stack, cast(Node!(u32)*)null); !CheckNil(null, n); n = SPop(&stack, cast(Node!(u32)*)null), count -= 1) { assert(n.value == count); } assert(stack.first == null); } }