commit 78f58a8fc7796a7684f898b857e0605711150592 Author: Rekai Musuka Date: Thu Mar 9 23:41:46 2023 -0600 feat: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e73c965 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache/ +zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..9345628 --- /dev/null +++ b/build.zig @@ -0,0 +1,44 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const lib = b.addStaticLibrary(.{ + .name = "zba-util", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/lib.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the library to be installed into the standard + // location when the user invokes the "install" step (the default step when + // running `zig build`). + lib.install(); + + // Creates a step for unit testing. + const main_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build test` + // This will evaluate the `test` step rather than the default, which is "install". + const test_step = b.step("test", "Run library tests"); + test_step.dependOn(&main_tests.step); +} diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000..86dc023 --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,69 @@ +const std = @import("std"); + +pub fn RingBuffer(comptime T: type) type { + return struct { + const Self = @This(); + const Index = usize; + + const Atomic = std.atomic.Atomic; + + const max_capacity = (@as(Index, 1) << @typeInfo(Index).Int.bits - 1) - 1; // half the range of index type + const log = std.log.scoped(.RingBuffer); + + read: Atomic(Index), + write: Atomic(Index), + buf: []T, + + const Error = error{buffer_full}; + + pub fn init(buf: []T) Self { + std.debug.assert(std.math.isPowerOfTwo(buf.len)); // capacity must be a power of two + std.debug.assert(buf.len <= max_capacity); + + return .{ + .read = Atomic(Index).init(0), + .write = Atomic(Index).init(0), + .buf = buf, + }; + } + + pub fn push(self: *Self, value: T) Error!void { + const read_idx = self.read.load(.Acquire); + const write_idx = self.write.load(.Acquire); + + // Check to see if Queue is full + if (write_idx - read_idx == self.buf.len) return Error.buffer_full; + + self.buf[self.mask(write_idx)] = value; + + std.atomic.fence(.Release); + self.write.store(write_idx + 1, .Release); + } + + pub fn pop(self: *Self) ?T { + const read_idx = self.read.load(.Acquire); + const write_idx = self.write.load(.Acquire); + + if (read_idx == write_idx) return null; + + std.atomic.fence(.Acquire); + const value = self.buf[self.mask(self.read)]; + + std.atomic.fence(.Release); + self.read.store(read_idx + 1, .Release); + + return value; + } + + pub fn len(self: *const Self) Index { + const read_idx = self.read.load(.Acquire); + const write_idx = self.write.load(.Acquire); + + return write_idx - read_idx; + } + + fn mask(self: *const Self, idx: Index) Index { + return idx & (self.buf.len - 1); + } + }; +}