|
| 1 | +//! This ring buffer stores read and write indices while being able to utilise |
| 2 | +//! the full backing slice by incrementing the indices modulo twice the slice's |
| 3 | +//! length and reducing indices modulo the slice's length on slice access. This |
| 4 | +//! means that whether the ring buffer if full or empty can be distinguished by |
| 5 | +//! looking at the difference between the read and write indices without adding |
| 6 | +//! an extra boolean flag or having to reserve a slot in the buffer. |
| 7 | +//! |
| 8 | +//! This ring buffer has not been implemented with thread safety in mind, and |
| 9 | +//! therefore should not be assumed to be suitable for use cases involving |
| 10 | +//! separate reader and writer threads. |
| 11 | + |
| 12 | +const Allocator = @import("std").mem.Allocator; |
| 13 | +const assert = @import("std").debug.assert; |
| 14 | + |
| 15 | +const RingBuffer = @This(); |
| 16 | + |
| 17 | +data: []u8, |
| 18 | +read_index: usize, |
| 19 | +write_index: usize, |
| 20 | + |
| 21 | +pub const Error = error{Full}; |
| 22 | + |
| 23 | +/// Allocate a new `RingBuffer`; `deinit()` should be called to free the buffer. |
| 24 | +pub fn init(allocator: Allocator, capacity: usize) Allocator.Error!RingBuffer { |
| 25 | + const bytes = try allocator.alloc(u8, capacity); |
| 26 | + return RingBuffer{ |
| 27 | + .data = bytes, |
| 28 | + .write_index = 0, |
| 29 | + .read_index = 0, |
| 30 | + }; |
| 31 | +} |
| 32 | + |
| 33 | +/// Free the data backing a `RingBuffer`; must be passed the same `Allocator` as |
| 34 | +/// `init()`. |
| 35 | +pub fn deinit(self: *RingBuffer, allocator: Allocator) void { |
| 36 | + allocator.free(self.data); |
| 37 | + self.* = undefined; |
| 38 | +} |
| 39 | + |
| 40 | +/// Returns `index` modulo the length of the backing slice. |
| 41 | +pub fn mask(self: RingBuffer, index: usize) usize { |
| 42 | + return index % self.data.len; |
| 43 | +} |
| 44 | + |
| 45 | +/// Returns `index` modulo twice the length of the backing slice. |
| 46 | +pub fn mask2(self: RingBuffer, index: usize) usize { |
| 47 | + return index % (2 * self.data.len); |
| 48 | +} |
| 49 | + |
| 50 | +/// Write `byte` into the ring buffer. Returns `error.Full` if the ring |
| 51 | +/// buffer is full. |
| 52 | +pub fn write(self: *RingBuffer, byte: u8) Error!void { |
| 53 | + if (self.isFull()) return error.Full; |
| 54 | + self.writeAssumeCapacity(byte); |
| 55 | +} |
| 56 | + |
| 57 | +/// Write `byte` into the ring buffer. If the ring buffer is full, the |
| 58 | +/// oldest byte is overwritten. |
| 59 | +pub fn writeAssumeCapacity(self: *RingBuffer, byte: u8) void { |
| 60 | + self.data[self.mask(self.write_index)] = byte; |
| 61 | + self.write_index = self.mask2(self.write_index + 1); |
| 62 | +} |
| 63 | + |
| 64 | +/// Write `bytes` into the ring buffer. Returns `error.Full` if the ring |
| 65 | +/// buffer does not have enough space, without writing any data. |
| 66 | +pub fn writeSlice(self: *RingBuffer, bytes: []const u8) Error!void { |
| 67 | + if (self.len() + bytes.len > self.data.len) return error.Full; |
| 68 | + self.writeSliceAssumeCapacity(bytes); |
| 69 | +} |
| 70 | + |
| 71 | +/// Write `bytes` into the ring buffer. If there is not enough space, older |
| 72 | +/// bytes will be overwritten. |
| 73 | +pub fn writeSliceAssumeCapacity(self: *RingBuffer, bytes: []const u8) void { |
| 74 | + for (bytes) |b| self.writeAssumeCapacity(b); |
| 75 | +} |
| 76 | + |
| 77 | +/// Consume a byte from the ring buffer and return it. Returns `null` if the |
| 78 | +/// ring buffer is empty. |
| 79 | +pub fn read(self: *RingBuffer) ?u8 { |
| 80 | + if (self.isEmpty()) return null; |
| 81 | + return self.readAssumeLength(); |
| 82 | +} |
| 83 | + |
| 84 | +/// Consume a byte from the ring buffer and return it; asserts that the buffer |
| 85 | +/// is not empty. |
| 86 | +pub fn readAssumeLength(self: *RingBuffer) u8 { |
| 87 | + assert(!self.isEmpty()); |
| 88 | + const byte = self.data[self.mask(self.read_index)]; |
| 89 | + self.read_index = self.mask2(self.read_index + 1); |
| 90 | + return byte; |
| 91 | +} |
| 92 | + |
| 93 | +/// Returns `true` if the ring buffer is empty and `false` otherwise. |
| 94 | +pub fn isEmpty(self: RingBuffer) bool { |
| 95 | + return self.write_index == self.read_index; |
| 96 | +} |
| 97 | + |
| 98 | +/// Returns `true` if the ring buffer is full and `false` otherwise. |
| 99 | +pub fn isFull(self: RingBuffer) bool { |
| 100 | + return self.mask2(self.write_index + self.data.len) == self.read_index; |
| 101 | +} |
| 102 | + |
| 103 | +/// Returns the length |
| 104 | +pub fn len(self: RingBuffer) usize { |
| 105 | + const wrap_offset = 2 * self.data.len * @boolToInt(self.write_index < self.read_index); |
| 106 | + const adjusted_write_index = self.write_index + wrap_offset; |
| 107 | + return adjusted_write_index - self.read_index; |
| 108 | +} |
| 109 | + |
| 110 | +/// A `Slice` represents a region of a ring buffer. The region is split into two |
| 111 | +/// sections as the ring buffer data will not be contiguous if the desired |
| 112 | +/// region wraps to the start of the backing slice. |
| 113 | +pub const Slice = struct { |
| 114 | + first: []u8, |
| 115 | + second: []u8, |
| 116 | +}; |
| 117 | + |
| 118 | +/// Returns a `Slice` for the region of the ring buffer starting at |
| 119 | +/// `self.mask(start_unmasked)` with the specified length. |
| 120 | +pub fn sliceAt(self: RingBuffer, start_unmasked: usize, length: usize) Slice { |
| 121 | + assert(length <= self.data.len); |
| 122 | + const slice1_start = self.mask(start_unmasked); |
| 123 | + const slice1_end = @min(self.data.len, slice1_start + length); |
| 124 | + const slice1 = self.data[slice1_start..slice1_end]; |
| 125 | + const slice2 = self.data[0 .. length - slice1.len]; |
| 126 | + return Slice{ |
| 127 | + .first = slice1, |
| 128 | + .second = slice2, |
| 129 | + }; |
| 130 | +} |
| 131 | + |
| 132 | +/// Returns a `Slice` for the last `length` bytes written to the ring buffer. |
| 133 | +/// Does not check that any bytes have been written into the region. |
| 134 | +pub fn sliceLast(self: RingBuffer, length: usize) Slice { |
| 135 | + return self.sliceAt(self.write_index + self.data.len - length, length); |
| 136 | +} |
0 commit comments