authorgravatar for 4678790+dweiller@users.noreply.github.comDominic <4678790+dweiller@users.noreply.github.com> 2023-02-21 12:07:44+11:00
committergravatar for 4678790+dweiller@users.noreply.github.comDominic <4678790+dweiller@users.noreply.github.com> 2023-02-21 12:58:34+11:00
logc7c35bf9e61035ead3098d109dc894b285373622
treeb70c5924b82efb6f44d8ec74e524fc5a86b3755a
parent1c518bd993b159d80a24925dc09ae7da5035ee05

std.RingBuffer: add (non-concurrent) RingBuffer implementation


6 files changed, 140 insertions(+), 128 deletions(-)

lib/std/RingBuffer.zig created+136
...@@ -0,0 +1,136 @@
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
12const Allocator = @import("std").mem.Allocator;
13const assert = @import("std").debug.assert;
14
15const RingBuffer = @This();
16
17data: []u8,
18read_index: usize,
19write_index: usize,
20
21pub const Error = error{Full};
22
23/// Allocate a new `RingBuffer`; `deinit()` should be called to free the buffer.
24pub 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()`.
35pub 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.
41pub 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.
46pub 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.
52pub 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.
59pub 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.
66pub 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.
73pub 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.
79pub 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.
86pub 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.
94pub 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.
99pub fn isFull(self: RingBuffer) bool {
100 return self.mask2(self.write_index + self.data.len) == self.read_index;
101}
102
103/// Returns the length
104pub 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.
113pub 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.
120pub 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.
134pub fn sliceLast(self: RingBuffer, length: usize) Slice {
135 return self.sliceAt(self.write_index + self.data.len - length, length);
136}
lib/std/compress/zstandard.zig+1-1
...@@ -1,11 +1,11 @@...@@ -1,11 +1,11 @@
1const std = @import("std");1const std = @import("std");
2const Allocator = std.mem.Allocator;2const Allocator = std.mem.Allocator;
3const RingBuffer = std.RingBuffer;
34
4const types = @import("zstandard/types.zig");5const types = @import("zstandard/types.zig");
5pub const frame = types.frame;6pub const frame = types.frame;
6pub const compressed_block = types.compressed_block;7pub const compressed_block = types.compressed_block;
78
8const RingBuffer = @import("zstandard/RingBuffer.zig");
9pub const decompress = @import("zstandard/decompress.zig");9pub const decompress = @import("zstandard/decompress.zig");
1010
11pub fn DecompressStream(11pub fn DecompressStream(
lib/std/compress/zstandard/RingBuffer.zig deleted-122
...@@ -1,122 +0,0 @@
1//! This ring buffer stores read and write indices while being able to utilise the full
2//! backing slice by incrementing the indices modulo twice the slice's length and reducing
3//! indices modulo the slice's length on slice access. This means that whether the ring buffer
4//! if full or empty can be distinguised by looking at the different between the read and write
5//! indices without adding an extra boolean flag or having to reserve a slot in the buffer.
6
7const Allocator = @import("std").mem.Allocator;
8const assert = @import("std").debug.assert;
9
10const RingBuffer = @This();
11
12data: []u8,
13read_index: usize,
14write_index: usize,
15
16pub const Error = error{Full};
17
18/// Allocate a new `RingBuffer`
19pub fn init(allocator: Allocator, capacity: usize) Allocator.Error!RingBuffer {
20 const bytes = try allocator.alloc(u8, capacity);
21 return RingBuffer{
22 .data = bytes,
23 .write_index = 0,
24 .read_index = 0,
25 };
26}
27
28/// Free a `RingBuffer`
29pub fn deinit(self: *RingBuffer, allocator: Allocator) void {
30 allocator.free(self.data);
31 self.* = undefined;
32}
33
34/// Returns `index` modulo the length of the backing slice.
35pub fn mask(self: RingBuffer, index: usize) usize {
36 return index % self.data.len;
37}
38
39/// Returns `index` module twice the length of the backing slice.
40pub fn mask2(self: RingBuffer, index: usize) usize {
41 return index % (2 * self.data.len);
42}
43
44/// Write `byte` into the ring buffer. Returns `error.Full` if the ring
45/// buffer is full.
46pub fn write(self: *RingBuffer, byte: u8) Error!void {
47 if (self.isFull()) return error.Full;
48 self.writeAssumeCapacity(byte);
49}
50
51/// Write `byte` into the ring buffer. If the ring buffer is full, the
52/// oldest byte is overwritten.
53pub fn writeAssumeCapacity(self: *RingBuffer, byte: u8) void {
54 self.data[self.mask(self.write_index)] = byte;
55 self.write_index = self.mask2(self.write_index + 1);
56}
57
58/// Write `bytes` into the ring bufffer. Returns `error.Full` if the ring
59/// buffer does not have enough space, without writing any data.
60pub fn writeSlice(self: *RingBuffer, bytes: []const u8) Error!void {
61 if (self.len() + bytes.len > self.data.len) return error.Full;
62 self.writeSliceAssumeCapacity(bytes);
63}
64
65/// Write `bytes` into the ring buffer. If there is not enough space, older
66/// bytes will be overwritten.
67pub fn writeSliceAssumeCapacity(self: *RingBuffer, bytes: []const u8) void {
68 for (bytes) |b| self.writeAssumeCapacity(b);
69}
70
71/// Consume a byte from the ring buffer and return it. Returns `null` if the
72/// ring buffer is empty.
73pub fn read(self: *RingBuffer) ?u8 {
74 if (self.isEmpty()) return null;
75 const byte = self.data[self.mask(self.read_index)];
76 self.read_index = self.mask2(self.read_index + 1);
77 return byte;
78}
79
80/// Returns `true` if the ring buffer is empty and `false` otherwise.
81pub fn isEmpty(self: RingBuffer) bool {
82 return self.write_index == self.read_index;
83}
84
85/// Returns `true` if the ring buffer is full and `false` otherwise.
86pub fn isFull(self: RingBuffer) bool {
87 return self.mask2(self.write_index + self.data.len) == self.read_index;
88}
89
90/// Returns the length
91pub fn len(self: RingBuffer) usize {
92 const wrap_offset = 2 * self.data.len * @boolToInt(self.write_index < self.read_index);
93 const adjusted_write_index = self.write_index + wrap_offset;
94 return adjusted_write_index - self.read_index;
95}
96
97/// A `Slice` represents a region of a ring buffer. The region is split into two
98/// sections as the ring buffer data will not be contiguous if the desired region
99/// wraps to the start of the backing slice.
100pub const Slice = struct {
101 first: []u8,
102 second: []u8,
103};
104
105/// Returns a `Slice` for the region of the ring buffer staring at `self.mask(start_unmasked)`
106/// with the specified length.
107pub fn sliceAt(self: RingBuffer, start_unmasked: usize, length: usize) Slice {
108 assert(length <= self.data.len);
109 const slice1_start = self.mask(start_unmasked);
110 const slice1_end = @min(self.data.len, slice1_start + length);
111 const slice1 = self.data[slice1_start..slice1_end];
112 const slice2 = self.data[0 .. length - slice1.len];
113 return Slice{
114 .first = slice1,
115 .second = slice2,
116 };
117}
118
119/// Returns a `Slice` for the last `length` bytes written to the ring buffer.
120pub fn sliceLast(self: RingBuffer, length: usize) Slice {
121 return self.sliceAt(self.write_index + self.data.len - length, length);
122}
lib/std/compress/zstandard/decode/block.zig+1-3
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const std = @import("std");1const std = @import("std");
2const assert = std.debug.assert;2const assert = std.debug.assert;
3const RingBuffer = std.RingBuffer;
34
4const types = @import("../types.zig");5const types = @import("../types.zig");
5const frame = types.frame;6const frame = types.frame;
...@@ -8,9 +9,6 @@ const LiteralsSection = types.compressed_block.LiteralsSection;...@@ -8,9 +9,6 @@ const LiteralsSection = types.compressed_block.LiteralsSection;
8const SequencesSection = types.compressed_block.SequencesSection;9const SequencesSection = types.compressed_block.SequencesSection;
910
10const huffman = @import("huffman.zig");11const huffman = @import("huffman.zig");
11
12const RingBuffer = @import("../RingBuffer.zig");
13
14const readers = @import("../readers.zig");12const readers = @import("../readers.zig");
1513
16const decodeFseTable = @import("fse.zig").decodeFseTable;14const decodeFseTable = @import("fse.zig").decodeFseTable;
lib/std/compress/zstandard/decompress.zig+1-2
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const assert = std.debug.assert;2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;3const Allocator = std.mem.Allocator;
4const RingBuffer = std.RingBuffer;
45
5const types = @import("types.zig");6const types = @import("types.zig");
6const frame = types.frame;7const frame = types.frame;
...@@ -12,8 +13,6 @@ const Table = types.compressed_block.Table;...@@ -12,8 +13,6 @@ const Table = types.compressed_block.Table;
1213
13pub const block = @import("decode/block.zig");14pub const block = @import("decode/block.zig");
1415
15pub const RingBuffer = @import("RingBuffer.zig");
16
17const readers = @import("readers.zig");16const readers = @import("readers.zig");
1817
19const readInt = std.mem.readIntLittle;18const readInt = std.mem.readIntLittle;
lib/std/std.zig+1
...@@ -31,6 +31,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE...@@ -31,6 +31,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE
31pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;31pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
32pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;32pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
33pub const Progress = @import("Progress.zig");33pub const Progress = @import("Progress.zig");
34pub const RingBuffer = @import("RingBuffer.zig");
34pub const SegmentedList = @import("segmented_list.zig").SegmentedList;35pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
35pub const SemanticVersion = @import("SemanticVersion.zig");36pub const SemanticVersion = @import("SemanticVersion.zig");
36pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;37pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;