add some common utilities

This commit is contained in:
Vivianne 2022-07-05 21:23:29 -07:00
parent ca34198c6d
commit cc91df4459
3 changed files with 315 additions and 0 deletions

104
src/common/fnv1a.zig Normal file
View File

@ -0,0 +1,104 @@
const testing = @import("std").testing;
pub fn toUpper(x: u8) u8 {
var c: i32 = x;
const lo: i32 = ('a' - 1) - c; // c >= 'a'
const hi: i32 = c - ('z' + 1); // c <= 'z'
const mask = (lo & hi) >> 9; // &&
c = c + (mask & ('A' - 'a'));
return @intCast(u8, c);
}
pub const Hasher32 = Hasher(u32, 16777619, 2166136261);
pub const Hasher64 = Hasher(u64, 1099511628211, 14695981039346656037);
pub const Hasher128 = Hasher(u128, 309485009821345068724781371, 144066263297769815596495629667062367629);
pub fn Hasher(comptime T: type, comptime prime: T, comptime bias: T) type {
return struct {
pub const Prime = prime;
pub const Bias = bias;
pub fn byteIgnoreCase(x: u8, hash: T) T {
return (hash ^ toUpper(x)) *% Prime;
}
pub fn byte(x: u8, hash: T) T {
return (hash ^ x) *% Prime;
}
pub fn word(x: u16, hash_in: T) T {
var hash = (hash_in ^ (0xff & (x >> 0))) *% Prime;
hash = (hash ^ (0xff & (x >> 8))) *% Prime;
return hash;
}
pub fn dword(x: u32, hash_in: T) T {
var hash = (hash_in ^ (0xff & (x >> 0))) *% Prime;
hash = (hash ^ (0xff & (x >> 8))) *% Prime;
hash = (hash ^ (0xff & (x >> 16))) *% Prime;
hash = (hash ^ (0xff & (x >> 24))) *% Prime;
return hash;
}
pub fn qword(x: u64, hash_in: T) T {
var hash = (hash_in ^ (0xff & (x >> 0))) *% Prime;
hash = (hash ^ (0xff & (x >> 8))) *% Prime;
hash = (hash ^ (0xff & (x >> 16))) *% Prime;
hash = (hash ^ (0xff & (x >> 24))) *% Prime;
hash = (hash ^ (0xff & (x >> 32))) *% Prime;
hash = (hash ^ (0xff & (x >> 40))) *% Prime;
hash = (hash ^ (0xff & (x >> 48))) *% Prime;
hash = (hash ^ (0xff & (x >> 56))) *% Prime;
return hash;
}
pub fn string(str: []const u8, hash_in: T) T {
var hash = hash_in;
for (str) |c| {
hash = byteIgnoreCase(c, hash);
}
return hash;
}
pub fn bytes(ptr: *u8, n_bytes: usize, hash: T) T {
var i = 0;
while (i < n_bytes) : (i += 1) {
hash = byte(ptr[i], hash);
}
return hash;
}
pub fn hashStr(text: []const u8) T {
var hash: T = 0;
if (text[0] != 0) {
hash = string(text, Bias);
hash = if (hash != 0) hash else 1;
}
return hash;
}
pub fn hashBytes(ptr: *u8, n_bytes: usize) T {
var hash: T = 0;
if (n_bytes > 0) {
hash = bytes(ptr, n_bytes, Bias);
hash = if (hash != 0) hash else 1;
}
return hash;
}
};
}
test "hashing 32-bit" {
try testing.expectEqual(@intCast(u32, 0x9de507a8), Hasher32.hashStr("foobar"));
try testing.expectEqual(@intCast(u32, 0x9de507a8), Hasher32.hashStr("FOObAR"));
}
test "hashing 64-bit" {
try testing.expectEqual(@intCast(u64, 0xed91fadfa4bbf528), Hasher64.hashStr("foobar"));
try testing.expectEqual(@intCast(u64, 0xed91fadfa4bbf528), Hasher64.hashStr("FOObAR"));
}

130
src/common/profiler.zig Normal file
View File

@ -0,0 +1,130 @@
const std = @import("std");
const assert = std.debug.assert;
const testing = std.testing;
const time = @import("time.zig");
const Hasher64 = @import("fnv1a.zig").Hasher64;
// TODO memory
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
// eventually the linear allocator will run out
const node_limit = 1024;
// eventually the call stack will run out in the Gui
const depth_limit = 20;
threadlocal var ctx = Context{};
const Context = struct {
prevroot: ?*const Node = null,
root: Node = Node{},
current: ?*Node = null,
frame: u64 = 0,
count: u64 = 0,
depth: i32 = 0,
};
const Node = struct {
mark: ?*const ProfileMark = null,
parent: ?*Node = null,
fchild: ?*Node = null,
lchild: ?*Node = null,
sibling: ?*const Node = null,
begin: time.Instant = undefined,
end: time.Instant = undefined,
hash: u64 = 0,
depth: i32 = 0,
};
pub const ProfileMark = struct {
const Self = @This();
name: []const u8,
calls: u32 = 0,
sum: u64 = 0,
pub fn init(name: []const u8) Self {
return Self{
.name = name,
};
}
pub fn begin(self: *const Self) !void {
const frame = time.getFrameCount();
if (frame != ctx.frame) {
ctx.current = &ctx.root;
ctx.frame = frame;
ctx.count = 0;
ctx.prevroot = &ctx.root;
ctx.root = .{ .hash = Hasher64.Bias };
ctx.depth = 0;
}
ctx.count += 1;
ctx.depth += 1;
if (ctx.count < node_limit and ctx.depth < depth_limit) {
const parent = ctx.current orelse &ctx.root;
const next = try allocator.create(Node);
next.mark = self;
next.parent = parent;
next.depth = ctx.depth;
var prev_hash = parent.hash;
const sibling = parent.lchild;
if (sibling) |s| {
s.sibling = next;
prev_hash = s.hash;
} else {
parent.fchild = next;
}
parent.lchild = next;
next.hash = Hasher64.string(self.name, prev_hash) | 1;
ctx.current = next;
next.begin = time.now();
}
}
pub fn end(self: *const Self) void {
const now = time.now();
if (ctx.count < node_limit and ctx.depth < depth_limit) {
const cur = ctx.current orelse unreachable;
assert(cur.mark == self);
assert(cur.depth == ctx.depth);
assert(cur.parent orelse null != null);
assert(cur.hash != 0);
cur.end = now;
ctx.current = cur.parent;
}
ctx.depth -= 1;
}
// TODO gui shit
};
const Testing = struct {
const pm_slow_func = ProfileMark.init("Test.slow_func");
fn slow_func() !void {
try pm_slow_func.begin();
defer pm_slow_func.end();
std.time.sleep(100);
}
};
test "profile test" {
try Testing.slow_func();
const fchild = ctx.root.fchild orelse unreachable;
try testing.expectEqual(std.math.Order.lt, fchild.begin.order(fchild.end));
try testing.expect(fchild.end.since(fchild.begin) >= 100);
try testing.expectEqual(Hasher64.string("test.slow_func", 0) | 1, fchild.hash);
}

81
src/common/time.zig Normal file
View File

@ -0,0 +1,81 @@
const std = @import("std");
const time = std.time;
const testing = std.testing;
pub fn lerp(comptime T: type, a: T, b: T, t: T) T {
return (a + (b - a)) * t;
}
pub const Instant = time.Instant;
var m_app_start: time.Instant = undefined;
var m_update: time.Instant = undefined;
var m_prev_update: time.Instant = undefined;
var m_dt: u64 = undefined;
var m_dtf: f32 = undefined;
var m_smooth_dtf: f32 = undefined;
var m_frame_count: u32 = undefined;
pub fn init() void {
m_frame_count = 0;
m_app_start = now();
m_update = m_app_start;
}
pub fn update() void {
m_frame_count += 1;
const now_time = now();
m_dt = now_time.since(m_prev_update);
m_prev_update = m_update;
m_update = now_time;
m_dtf = @intToFloat(f32, m_dt) / time.ms_per_s;
m_smooth_dtf = lerp(f32, m_smooth_dtf, m_dtf, (1.0 / 120.0));
}
pub fn deinit() void {}
pub fn now() time.Instant {
return time.Instant.now() catch unreachable;
}
pub fn lap(tick: *time.Instant) u64 {
const prev = tick;
tick.* = now();
return prev;
}
pub fn getFrameCount() u64 {
return m_frame_count;
}
pub fn getAppStart() u64 {
return m_app_start;
}
pub fn getPrevFrame() time.Instant {
return m_prev_update;
}
pub fn getDelta() u64 {
return m_dt;
}
pub fn deltaf() f32 {
return m_dtf;
}
pub fn smoothDeltaf() f32 {
return m_smooth_dtf;
}
test "time tests" {
init();
defer deinit();
time.sleep(10);
update();
try testing.expect(m_app_start.order(m_update) == .lt);
time.sleep(10);
update();
try testing.expect(m_app_start.order(m_update) == .lt);
try testing.expect(m_prev_update.order(m_update) == .lt);
}