From cc91df4459d966b38a8cbb7e040b5e436e1a3c38 Mon Sep 17 00:00:00 2001 From: Vivianne Langdon Date: Tue, 5 Jul 2022 21:23:29 -0700 Subject: [PATCH] add some common utilities --- src/common/fnv1a.zig | 104 ++++++++++++++++++++++++++++++++ src/common/profiler.zig | 130 ++++++++++++++++++++++++++++++++++++++++ src/common/time.zig | 81 +++++++++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100644 src/common/fnv1a.zig create mode 100644 src/common/profiler.zig create mode 100644 src/common/time.zig diff --git a/src/common/fnv1a.zig b/src/common/fnv1a.zig new file mode 100644 index 0000000..849b73a --- /dev/null +++ b/src/common/fnv1a.zig @@ -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")); +} diff --git a/src/common/profiler.zig b/src/common/profiler.zig new file mode 100644 index 0000000..0c02857 --- /dev/null +++ b/src/common/profiler.zig @@ -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); +} diff --git a/src/common/time.zig b/src/common/time.zig new file mode 100644 index 0000000..473c78d --- /dev/null +++ b/src/common/time.zig @@ -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); +}