commit c7c35bf9e61035ead3098d109dc894b285373622 (tree)
parent 1c518bd993b159d80a24925dc09ae7da5035ee05
Author: dweiller <4678790+dweiller@users.noreplay.github.com>
Date: Tue, 21 Feb 2023 12:07:44 +1100
std.RingBuffer: add (non-concurrent) RingBuffer implementation
Diffstat:
6 files changed, 140 insertions(+), 128 deletions(-)
diff --git a/lib/std/RingBuffer.zig b/lib/std/RingBuffer.zig
@@ -0,0 +1,136 @@
+//! This ring buffer stores read and write indices while being able to utilise
+//! the full backing slice by incrementing the indices modulo twice the slice's
+//! length and reducing indices modulo the slice's length on slice access. This
+//! means that whether the ring buffer if full or empty can be distinguished by
+//! looking at the difference between the read and write indices without adding
+//! an extra boolean flag or having to reserve a slot in the buffer.
+//!
+//! This ring buffer has not been implemented with thread safety in mind, and
+//! therefore should not be assumed to be suitable for use cases involving
+//! separate reader and writer threads.
+
+const Allocator = @import("std").mem.Allocator;
+const assert = @import("std").debug.assert;
+
+const RingBuffer = @This();
+
+data: []u8,
+read_index: usize,
+write_index: usize,
+
+pub const Error = error{Full};
+
+/// Allocate a new `RingBuffer`; `deinit()` should be called to free the buffer.
+pub fn init(allocator: Allocator, capacity: usize) Allocator.Error!RingBuffer {
+ const bytes = try allocator.alloc(u8, capacity);
+ return RingBuffer{
+ .data = bytes,
+ .write_index = 0,
+ .read_index = 0,
+ };
+}
+
+/// Free the data backing a `RingBuffer`; must be passed the same `Allocator` as
+/// `init()`.
+pub fn deinit(self: *RingBuffer, allocator: Allocator) void {
+ allocator.free(self.data);
+ self.* = undefined;
+}
+
+/// Returns `index` modulo the length of the backing slice.
+pub fn mask(self: RingBuffer, index: usize) usize {
+ return index % self.data.len;
+}
+
+/// Returns `index` modulo twice the length of the backing slice.
+pub fn mask2(self: RingBuffer, index: usize) usize {
+ return index % (2 * self.data.len);
+}
+
+/// Write `byte` into the ring buffer. Returns `error.Full` if the ring
+/// buffer is full.
+pub fn write(self: *RingBuffer, byte: u8) Error!void {
+ if (self.isFull()) return error.Full;
+ self.writeAssumeCapacity(byte);
+}
+
+/// Write `byte` into the ring buffer. If the ring buffer is full, the
+/// oldest byte is overwritten.
+pub fn writeAssumeCapacity(self: *RingBuffer, byte: u8) void {
+ self.data[self.mask(self.write_index)] = byte;
+ self.write_index = self.mask2(self.write_index + 1);
+}
+
+/// Write `bytes` into the ring buffer. Returns `error.Full` if the ring
+/// buffer does not have enough space, without writing any data.
+pub fn writeSlice(self: *RingBuffer, bytes: []const u8) Error!void {
+ if (self.len() + bytes.len > self.data.len) return error.Full;
+ self.writeSliceAssumeCapacity(bytes);
+}
+
+/// Write `bytes` into the ring buffer. If there is not enough space, older
+/// bytes will be overwritten.
+pub fn writeSliceAssumeCapacity(self: *RingBuffer, bytes: []const u8) void {
+ for (bytes) |b| self.writeAssumeCapacity(b);
+}
+
+/// Consume a byte from the ring buffer and return it. Returns `null` if the
+/// ring buffer is empty.
+pub fn read(self: *RingBuffer) ?u8 {
+ if (self.isEmpty()) return null;
+ return self.readAssumeLength();
+}
+
+/// Consume a byte from the ring buffer and return it; asserts that the buffer
+/// is not empty.
+pub fn readAssumeLength(self: *RingBuffer) u8 {
+ assert(!self.isEmpty());
+ const byte = self.data[self.mask(self.read_index)];
+ self.read_index = self.mask2(self.read_index + 1);
+ return byte;
+}
+
+/// Returns `true` if the ring buffer is empty and `false` otherwise.
+pub fn isEmpty(self: RingBuffer) bool {
+ return self.write_index == self.read_index;
+}
+
+/// Returns `true` if the ring buffer is full and `false` otherwise.
+pub fn isFull(self: RingBuffer) bool {
+ return self.mask2(self.write_index + self.data.len) == self.read_index;
+}
+
+/// Returns the length
+pub fn len(self: RingBuffer) usize {
+ const wrap_offset = 2 * self.data.len * @boolToInt(self.write_index < self.read_index);
+ const adjusted_write_index = self.write_index + wrap_offset;
+ return adjusted_write_index - self.read_index;
+}
+
+/// A `Slice` represents a region of a ring buffer. The region is split into two
+/// sections as the ring buffer data will not be contiguous if the desired
+/// region wraps to the start of the backing slice.
+pub const Slice = struct {
+ first: []u8,
+ second: []u8,
+};
+
+/// Returns a `Slice` for the region of the ring buffer starting at
+/// `self.mask(start_unmasked)` with the specified length.
+pub fn sliceAt(self: RingBuffer, start_unmasked: usize, length: usize) Slice {
+ assert(length <= self.data.len);
+ const slice1_start = self.mask(start_unmasked);
+ const slice1_end = @min(self.data.len, slice1_start + length);
+ const slice1 = self.data[slice1_start..slice1_end];
+ const slice2 = self.data[0 .. length - slice1.len];
+ return Slice{
+ .first = slice1,
+ .second = slice2,
+ };
+}
+
+/// Returns a `Slice` for the last `length` bytes written to the ring buffer.
+/// Does not check that any bytes have been written into the region.
+pub fn sliceLast(self: RingBuffer, length: usize) Slice {
+ return self.sliceAt(self.write_index + self.data.len - length, length);
+}
diff --git a/lib/std/compress/zstandard.zig b/lib/std/compress/zstandard.zig
@@ -1,11 +1,11 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
+const RingBuffer = std.RingBuffer;
const types = @import("zstandard/types.zig");
pub const frame = types.frame;
pub const compressed_block = types.compressed_block;
-const RingBuffer = @import("zstandard/RingBuffer.zig");
pub const decompress = @import("zstandard/decompress.zig");
pub fn DecompressStream(
diff --git a/lib/std/compress/zstandard/RingBuffer.zig b/lib/std/compress/zstandard/RingBuffer.zig
@@ -1,122 +0,0 @@
-//! This ring buffer stores read and write indices while being able to utilise the full
-//! backing slice by incrementing the indices modulo twice the slice's length and reducing
-//! indices modulo the slice's length on slice access. This means that whether the ring buffer
-//! if full or empty can be distinguised by looking at the different between the read and write
-//! indices without adding an extra boolean flag or having to reserve a slot in the buffer.
-
-const Allocator = @import("std").mem.Allocator;
-const assert = @import("std").debug.assert;
-
-const RingBuffer = @This();
-
-data: []u8,
-read_index: usize,
-write_index: usize,
-
-pub const Error = error{Full};
-
-/// Allocate a new `RingBuffer`
-pub fn init(allocator: Allocator, capacity: usize) Allocator.Error!RingBuffer {
- const bytes = try allocator.alloc(u8, capacity);
- return RingBuffer{
- .data = bytes,
- .write_index = 0,
- .read_index = 0,
- };
-}
-
-/// Free a `RingBuffer`
-pub fn deinit(self: *RingBuffer, allocator: Allocator) void {
- allocator.free(self.data);
- self.* = undefined;
-}
-
-/// Returns `index` modulo the length of the backing slice.
-pub fn mask(self: RingBuffer, index: usize) usize {
- return index % self.data.len;
-}
-
-/// Returns `index` module twice the length of the backing slice.
-pub fn mask2(self: RingBuffer, index: usize) usize {
- return index % (2 * self.data.len);
-}
-
-/// Write `byte` into the ring buffer. Returns `error.Full` if the ring
-/// buffer is full.
-pub fn write(self: *RingBuffer, byte: u8) Error!void {
- if (self.isFull()) return error.Full;
- self.writeAssumeCapacity(byte);
-}
-
-/// Write `byte` into the ring buffer. If the ring buffer is full, the
-/// oldest byte is overwritten.
-pub fn writeAssumeCapacity(self: *RingBuffer, byte: u8) void {
- self.data[self.mask(self.write_index)] = byte;
- self.write_index = self.mask2(self.write_index + 1);
-}
-
-/// Write `bytes` into the ring bufffer. Returns `error.Full` if the ring
-/// buffer does not have enough space, without writing any data.
-pub fn writeSlice(self: *RingBuffer, bytes: []const u8) Error!void {
- if (self.len() + bytes.len > self.data.len) return error.Full;
- self.writeSliceAssumeCapacity(bytes);
-}
-
-/// Write `bytes` into the ring buffer. If there is not enough space, older
-/// bytes will be overwritten.
-pub fn writeSliceAssumeCapacity(self: *RingBuffer, bytes: []const u8) void {
- for (bytes) |b| self.writeAssumeCapacity(b);
-}
-
-/// Consume a byte from the ring buffer and return it. Returns `null` if the
-/// ring buffer is empty.
-pub fn read(self: *RingBuffer) ?u8 {
- if (self.isEmpty()) return null;
- const byte = self.data[self.mask(self.read_index)];
- self.read_index = self.mask2(self.read_index + 1);
- return byte;
-}
-
-/// Returns `true` if the ring buffer is empty and `false` otherwise.
-pub fn isEmpty(self: RingBuffer) bool {
- return self.write_index == self.read_index;
-}
-
-/// Returns `true` if the ring buffer is full and `false` otherwise.
-pub fn isFull(self: RingBuffer) bool {
- return self.mask2(self.write_index + self.data.len) == self.read_index;
-}
-
-/// Returns the length
-pub fn len(self: RingBuffer) usize {
- const wrap_offset = 2 * self.data.len * @boolToInt(self.write_index < self.read_index);
- const adjusted_write_index = self.write_index + wrap_offset;
- return adjusted_write_index - self.read_index;
-}
-
-/// A `Slice` represents a region of a ring buffer. The region is split into two
-/// sections as the ring buffer data will not be contiguous if the desired region
-/// wraps to the start of the backing slice.
-pub const Slice = struct {
- first: []u8,
- second: []u8,
-};
-
-/// Returns a `Slice` for the region of the ring buffer staring at `self.mask(start_unmasked)`
-/// with the specified length.
-pub fn sliceAt(self: RingBuffer, start_unmasked: usize, length: usize) Slice {
- assert(length <= self.data.len);
- const slice1_start = self.mask(start_unmasked);
- const slice1_end = @min(self.data.len, slice1_start + length);
- const slice1 = self.data[slice1_start..slice1_end];
- const slice2 = self.data[0 .. length - slice1.len];
- return Slice{
- .first = slice1,
- .second = slice2,
- };
-}
-
-/// Returns a `Slice` for the last `length` bytes written to the ring buffer.
-pub fn sliceLast(self: RingBuffer, length: usize) Slice {
- return self.sliceAt(self.write_index + self.data.len - length, length);
-}
diff --git a/lib/std/compress/zstandard/decode/block.zig b/lib/std/compress/zstandard/decode/block.zig
@@ -1,5 +1,6 @@
const std = @import("std");
const assert = std.debug.assert;
+const RingBuffer = std.RingBuffer;
const types = @import("../types.zig");
const frame = types.frame;
@@ -8,9 +9,6 @@ const LiteralsSection = types.compressed_block.LiteralsSection;
const SequencesSection = types.compressed_block.SequencesSection;
const huffman = @import("huffman.zig");
-
-const RingBuffer = @import("../RingBuffer.zig");
-
const readers = @import("../readers.zig");
const decodeFseTable = @import("fse.zig").decodeFseTable;
diff --git a/lib/std/compress/zstandard/decompress.zig b/lib/std/compress/zstandard/decompress.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
+const RingBuffer = std.RingBuffer;
const types = @import("types.zig");
const frame = types.frame;
@@ -12,8 +13,6 @@ const Table = types.compressed_block.Table;
pub const block = @import("decode/block.zig");
-pub const RingBuffer = @import("RingBuffer.zig");
-
const readers = @import("readers.zig");
const readInt = std.mem.readIntLittle;
diff --git a/lib/std/std.zig b/lib/std/std.zig
@@ -31,6 +31,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE
pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
pub const Progress = @import("Progress.zig");
+pub const RingBuffer = @import("RingBuffer.zig");
pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
pub const SemanticVersion = @import("SemanticVersion.zig");
pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;