dlib/util.d
2025-09-02 02:48:38 +10:00

993 lines
15 KiB
D

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);
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);
}
}