const std = @import("std"); /// Generic slot with value type `T` for a ring buffer pub fn Slot(comptime T: type) type { return struct { /// Value value: T, /// Slot version version: std.atomic.Value(usize), }; } /// Ring buffer with value type `T` on a slice of slots pub fn RingBuffer(comptime T: type) type { return struct { const Self = @This(); /// Ring buffer capacity capacity: usize, /// Buffer slice buffer: []Slot(T), /// Atomic head index of buffer (write index) head: std.atomic.Value(usize) = std.atomic.Value(usize).init(0), /// Tail index of buffer (read index) tail: usize = 0, /// Initializes ring buffer with a given buffer, throws `error.CapacityTooSmall` if the buffer capacity is less or equal than 1. pub fn init(buffer: []Slot(T)) error{CapacityTooSmall}!Self { if (buffer.len <= 1) { return error.CapacityTooSmall; } for (buffer, 0..) |*slot, i| { slot.version = std.atomic.Value(usize).init(i); } return .{ .capacity = buffer.len, .buffer = buffer, }; } /// Enqueues element. Throws `error.Overflow` if no more elements fit. pub fn enqueue(self: *Self, value: T) error{Overflow}!void { while (true) { // Acquire write slot const head = self.head.load(.acquire); const index = head % self.capacity; const slot = &self.buffer[index]; // Check if slot has been read (empty) const expected_version = head; if (slot.version.load(.acquire) != expected_version) { return error.Overflow; } // Compare and swap head index if (self.head.cmpxchgStrong( head, head + 1, .seq_cst, .seq_cst, )) |_| { std.atomic.spinLoopHint(); // Retry again next cycle continue; } // Slot versioning slot.value = value; slot.version.store(expected_version + 1, .release); return; } } /// Dequeues element. Throws `error.Underflow` if ring buffer is empty. pub fn dequeue(self: *Self) error{Underflow}!T { // Acquire read slot const tail = self.tail; const index = tail % self.capacity; const slot = &self.buffer[index]; // Check is slot has been written to (full) const expected_version = tail + 1; if (slot.version.load(.acquire) != expected_version) { return error.Underflow; } // Slot versioning const value = slot.value; slot.version.store(tail +% self.capacity, .release); self.tail +%= 1; return value; } /// Dequeues all stores elements into a buffer. It must at least have a size of the buffer capacity. pub fn dequeueAll(self: *Self, buf: []T) ![]T { if (buf.len < self.capacity) { return error.BufferTooSmall; } const head = self.head.load(.acquire) % self.capacity; var tail = self.tail % self.capacity; var i: usize = 0; while (true) { const val = self.dequeue() catch break; buf[i] = val; i += 1; tail = (tail + 1) % self.capacity; if (tail == head) break; } return buf[0..i]; } }; }