mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
781 lines
24 KiB
Zig
781 lines
24 KiB
Zig
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const fastmem = @import("../fastmem.zig");
|
|
|
|
/// Returns a circular buffer containing type T.
|
|
pub fn CircBuf(comptime T: type, comptime default: T) type {
|
|
return struct {
|
|
const Self = @This();
|
|
|
|
// Implementation note: there's a lot of unsafe addition of usize
|
|
// here in this implementation that can technically overflow. If someone
|
|
// wants to fix this and make it overflow safe (use subtractions for
|
|
// checks prior to additions) then I welcome it. In reality, we'd
|
|
// have to be a really, really large terminal screen to even worry
|
|
// about this so I'm punting it.
|
|
|
|
storage: []T,
|
|
head: usize,
|
|
tail: usize,
|
|
|
|
// We could remove this and just use math with head/tail to figure
|
|
// it out, but our usage of circular buffers stores so much data that
|
|
// this minor overhead is not worth optimizing out.
|
|
full: bool,
|
|
|
|
pub const Iterator = struct {
|
|
buf: Self,
|
|
idx: usize,
|
|
direction: Direction,
|
|
|
|
pub const Direction = enum { forward, reverse };
|
|
|
|
pub fn next(self: *Iterator) ?*T {
|
|
if (self.idx >= self.buf.len()) return null;
|
|
|
|
// Get our index from the tail
|
|
const tail_idx = switch (self.direction) {
|
|
.forward => self.idx,
|
|
.reverse => self.buf.len() - self.idx - 1,
|
|
};
|
|
|
|
// Translate the tail index to a storage index
|
|
const storage_idx = (self.buf.tail + tail_idx) % self.buf.capacity();
|
|
self.idx += 1;
|
|
return &self.buf.storage[storage_idx];
|
|
}
|
|
|
|
/// Seek the iterator by a given amount. This will clamp
|
|
/// the values to the bounds of the buffer so overflows are
|
|
/// not possible.
|
|
pub fn seekBy(self: *Iterator, amount: isize) void {
|
|
if (amount > 0) {
|
|
self.idx +|= @intCast(amount);
|
|
} else {
|
|
self.idx -|= @intCast(@abs(amount));
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Initialize a new circular buffer that can store size elements.
|
|
pub fn init(alloc: Allocator, size: usize) Allocator.Error!Self {
|
|
const buf = try alloc.alloc(T, size);
|
|
@memset(buf, default);
|
|
|
|
return Self{
|
|
.storage = buf,
|
|
.head = 0,
|
|
.tail = 0,
|
|
.full = size == 0,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Self, alloc: Allocator) void {
|
|
alloc.free(self.storage);
|
|
self.* = undefined;
|
|
}
|
|
|
|
/// Append a single value to the buffer. If the buffer is full,
|
|
/// an error will be returned.
|
|
pub fn append(self: *Self, v: T) Allocator.Error!void {
|
|
if (self.full) return error.OutOfMemory;
|
|
self.storage[self.head] = v;
|
|
self.head += 1;
|
|
if (self.head >= self.storage.len) self.head = 0;
|
|
self.full = self.head == self.tail;
|
|
}
|
|
|
|
/// Append a slice to the buffer. If the buffer cannot fit the
|
|
/// entire slice then an error will be returned. It is up to the
|
|
/// caller to rotate the circular buffer if they want to overwrite
|
|
/// the oldest data.
|
|
pub fn appendSlice(
|
|
self: *Self,
|
|
slice: []const T,
|
|
) Allocator.Error!void {
|
|
const storage = self.getPtrSlice(self.len(), slice.len);
|
|
fastmem.copy(T, storage[0], slice[0..storage[0].len]);
|
|
fastmem.copy(T, storage[1], slice[storage[0].len..]);
|
|
}
|
|
|
|
/// Clear the buffer.
|
|
pub fn clear(self: *Self) void {
|
|
self.head = 0;
|
|
self.tail = 0;
|
|
self.full = false;
|
|
}
|
|
|
|
/// Iterate over the circular buffer.
|
|
pub fn iterator(self: Self, direction: Iterator.Direction) Iterator {
|
|
return Iterator{
|
|
.buf = self,
|
|
.idx = 0,
|
|
.direction = direction,
|
|
};
|
|
}
|
|
|
|
/// Get the first (oldest) value in the buffer.
|
|
pub fn first(self: Self) ?*T {
|
|
// Note: this can be more efficient by not using the
|
|
// iterator, but this was an easy way to implement it.
|
|
var it = self.iterator(.forward);
|
|
return it.next();
|
|
}
|
|
|
|
/// Get the last (newest) value in the buffer.
|
|
pub fn last(self: Self) ?*T {
|
|
// Note: this can be more efficient by not using the
|
|
// iterator, but this was an easy way to implement it.
|
|
var it = self.iterator(.reverse);
|
|
return it.next();
|
|
}
|
|
|
|
/// Ensures that there is enough capacity to store amount more
|
|
/// items via append.
|
|
pub fn ensureUnusedCapacity(
|
|
self: *Self,
|
|
alloc: Allocator,
|
|
amount: usize,
|
|
) Allocator.Error!void {
|
|
const new_cap = self.len() + amount;
|
|
if (new_cap <= self.capacity()) return;
|
|
try self.resize(alloc, new_cap);
|
|
}
|
|
|
|
/// Resize the buffer to the given size (larger or smaller).
|
|
/// If larger, new values will be set to the default value.
|
|
pub fn resize(self: *Self, alloc: Allocator, size: usize) Allocator.Error!void {
|
|
// Rotate to zero so it is aligned.
|
|
try self.rotateToZero(alloc);
|
|
|
|
// Reallocate, this adds to the end so we're ready to go.
|
|
const prev_len = self.len();
|
|
const prev_cap = self.storage.len;
|
|
self.storage = try alloc.realloc(self.storage, size);
|
|
|
|
// If we grew, we need to set our new defaults. We can add it
|
|
// at the end since we rotated to start.
|
|
if (size > prev_cap) {
|
|
@memset(self.storage[prev_cap..], default);
|
|
|
|
// Fix up our head/tail
|
|
if (self.full) {
|
|
self.head = prev_len;
|
|
self.full = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Rotate the data so that it is zero-aligned.
|
|
fn rotateToZero(self: *Self, alloc: Allocator) Allocator.Error!void {
|
|
// TODO: this does this in the worst possible way by allocating.
|
|
// rewrite to not allocate, its possible, I'm just lazy right now.
|
|
|
|
// If we're already at zero then do nothing.
|
|
if (self.tail == 0) return;
|
|
|
|
var buf = try alloc.alloc(T, self.storage.len);
|
|
defer {
|
|
self.head = if (self.full) 0 else self.len();
|
|
self.tail = 0;
|
|
alloc.free(self.storage);
|
|
self.storage = buf;
|
|
}
|
|
|
|
if (!self.full and self.head >= self.tail) {
|
|
fastmem.copy(T, buf, self.storage[self.tail..self.head]);
|
|
return;
|
|
}
|
|
|
|
const middle = self.storage.len - self.tail;
|
|
fastmem.copy(T, buf, self.storage[self.tail..]);
|
|
fastmem.copy(T, buf[middle..], self.storage[0..self.head]);
|
|
}
|
|
|
|
/// Returns if the buffer is currently empty. To check if its
|
|
/// full, just check the "full" attribute.
|
|
pub fn empty(self: Self) bool {
|
|
return !self.full and self.head == self.tail;
|
|
}
|
|
|
|
/// Returns the total capacity allocated for this buffer.
|
|
pub fn capacity(self: Self) usize {
|
|
return self.storage.len;
|
|
}
|
|
|
|
/// Returns the length in elements that are used.
|
|
pub fn len(self: Self) usize {
|
|
if (self.full) return self.storage.len;
|
|
if (self.head >= self.tail) return self.head - self.tail;
|
|
return self.storage.len - (self.tail - self.head);
|
|
}
|
|
|
|
/// Delete the oldest n values from the buffer. If there are less
|
|
/// than n values in the buffer, it'll delete everything.
|
|
pub fn deleteOldest(self: *Self, n: usize) void {
|
|
assert(n <= self.storage.len);
|
|
|
|
// Clear the values back to default
|
|
const slices = self.getPtrSlice(0, n);
|
|
inline for (slices) |slice| @memset(slice, default);
|
|
|
|
// If we're not full, we can just advance the tail. We know
|
|
// it'll be less than the length because otherwise we'd be full.
|
|
self.tail += @min(self.len(), n);
|
|
if (self.tail >= self.storage.len) self.tail -= self.storage.len;
|
|
self.full = false;
|
|
}
|
|
|
|
/// Returns a pointer to the value at offset with the given length,
|
|
/// and considers this full amount of data "written" if it is beyond
|
|
/// the end of our buffer. This never "rotates" the buffer because
|
|
/// the offset can only be within the size of the buffer.
|
|
pub fn getPtrSlice(self: *Self, offset: usize, slice_len: usize) [2][]T {
|
|
// Note: this assertion is very important, it hints the compiler
|
|
// which generates ~10% faster code than without it.
|
|
assert(offset + slice_len <= self.capacity());
|
|
|
|
// End offset is the last offset (exclusive) for our slice.
|
|
// We use exclusive because it makes the math easier and it
|
|
// matches Zigs slicing parameterization.
|
|
const end_offset = offset + slice_len;
|
|
|
|
// If our slice can't fit it in our length, then we need to advance.
|
|
if (end_offset > self.len()) self.advance(end_offset - self.len());
|
|
|
|
// Our start and end indexes into the storage buffer
|
|
const start_idx = self.storageOffset(offset);
|
|
const end_idx = self.storageOffset(end_offset - 1);
|
|
// std.log.warn("A={} B={}", .{ start_idx, end_idx });
|
|
|
|
// Optimistically, our data fits in one slice
|
|
if (end_idx >= start_idx) {
|
|
return .{
|
|
self.storage[start_idx .. end_idx + 1],
|
|
self.storage[0..0], // So there is an empty slice
|
|
};
|
|
}
|
|
|
|
return .{
|
|
self.storage[start_idx..],
|
|
self.storage[0 .. end_idx + 1],
|
|
};
|
|
}
|
|
|
|
/// Advances the head/tail so that we can store amount.
|
|
fn advance(self: *Self, amount: usize) void {
|
|
assert(amount <= self.storage.len - self.len());
|
|
|
|
// Optimistically add our amount
|
|
self.head += amount;
|
|
|
|
// If we exceeded the length of the buffer, wrap around.
|
|
if (self.head >= self.storage.len) self.head = self.head - self.storage.len;
|
|
|
|
// If we're full, we have to keep tail lined up.
|
|
if (self.full) self.tail = self.head;
|
|
|
|
// We're full if the head reached the tail. The head can never
|
|
// pass the tail because advance asserts amount is only in
|
|
// available space left
|
|
self.full = self.head == self.tail;
|
|
}
|
|
|
|
/// For a given offset from zero, this returns the offset in the
|
|
/// storage buffer where this data can be found.
|
|
fn storageOffset(self: Self, offset: usize) usize {
|
|
assert(offset < self.storage.len);
|
|
|
|
// This should be subtraction ideally to avoid overflows but
|
|
// it would take a really, really, huge buffer to overflow.
|
|
const fits_offset = self.tail + offset;
|
|
if (fits_offset < self.storage.len) return fits_offset;
|
|
return fits_offset - self.storage.len;
|
|
}
|
|
};
|
|
}
|
|
|
|
test {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 12);
|
|
defer buf.deinit(alloc);
|
|
|
|
try testing.expect(buf.empty());
|
|
try testing.expectEqual(@as(usize, 0), buf.len());
|
|
}
|
|
|
|
test "CircBuf append" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 3);
|
|
defer buf.deinit(alloc);
|
|
|
|
try buf.append(1);
|
|
try buf.append(2);
|
|
try buf.append(3);
|
|
try testing.expectError(error.OutOfMemory, buf.append(4));
|
|
buf.deleteOldest(1);
|
|
try buf.append(4);
|
|
try testing.expectError(error.OutOfMemory, buf.append(5));
|
|
}
|
|
|
|
test "CircBuf forward iterator" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 3);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Empty
|
|
{
|
|
var it = buf.iterator(.forward);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Partially full
|
|
try buf.append(1);
|
|
try buf.append(2);
|
|
{
|
|
var it = buf.iterator(.forward);
|
|
try testing.expect(it.next().?.* == 1);
|
|
try testing.expect(it.next().?.* == 2);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Full
|
|
try buf.append(3);
|
|
{
|
|
var it = buf.iterator(.forward);
|
|
try testing.expect(it.next().?.* == 1);
|
|
try testing.expect(it.next().?.* == 2);
|
|
try testing.expect(it.next().?.* == 3);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Delete and add
|
|
buf.deleteOldest(1);
|
|
try buf.append(4);
|
|
{
|
|
var it = buf.iterator(.forward);
|
|
try testing.expect(it.next().?.* == 2);
|
|
try testing.expect(it.next().?.* == 3);
|
|
try testing.expect(it.next().?.* == 4);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
}
|
|
|
|
test "CircBuf reverse iterator" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 3);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Empty
|
|
{
|
|
var it = buf.iterator(.reverse);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Partially full
|
|
try buf.append(1);
|
|
try buf.append(2);
|
|
{
|
|
var it = buf.iterator(.reverse);
|
|
try testing.expect(it.next().?.* == 2);
|
|
try testing.expect(it.next().?.* == 1);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Full
|
|
try buf.append(3);
|
|
{
|
|
var it = buf.iterator(.reverse);
|
|
try testing.expect(it.next().?.* == 3);
|
|
try testing.expect(it.next().?.* == 2);
|
|
try testing.expect(it.next().?.* == 1);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
|
|
// Delete and add
|
|
buf.deleteOldest(1);
|
|
try buf.append(4);
|
|
{
|
|
var it = buf.iterator(.reverse);
|
|
try testing.expect(it.next().?.* == 4);
|
|
try testing.expect(it.next().?.* == 3);
|
|
try testing.expect(it.next().?.* == 2);
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
}
|
|
|
|
test "CircBuf first/last" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 3);
|
|
defer buf.deinit(alloc);
|
|
|
|
try buf.append(1);
|
|
try buf.append(2);
|
|
try buf.append(3);
|
|
try testing.expectEqual(3, buf.last().?.*);
|
|
try testing.expectEqual(1, buf.first().?.*);
|
|
}
|
|
|
|
test "CircBuf first/last empty" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 0);
|
|
defer buf.deinit(alloc);
|
|
|
|
try testing.expect(buf.first() == null);
|
|
try testing.expect(buf.last() == null);
|
|
}
|
|
|
|
test "CircBuf first/last empty with cap" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 3);
|
|
defer buf.deinit(alloc);
|
|
|
|
try testing.expect(buf.first() == null);
|
|
try testing.expect(buf.last() == null);
|
|
}
|
|
|
|
test "CircBuf append slice" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 5);
|
|
defer buf.deinit(alloc);
|
|
|
|
try buf.appendSlice("hello");
|
|
{
|
|
var it = buf.iterator(.forward);
|
|
try testing.expect(it.next().?.* == 'h');
|
|
try testing.expect(it.next().?.* == 'e');
|
|
try testing.expect(it.next().?.* == 'l');
|
|
try testing.expect(it.next().?.* == 'l');
|
|
try testing.expect(it.next().?.* == 'o');
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
}
|
|
|
|
test "CircBuf append slice with wrap" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill the buffer
|
|
_ = buf.getPtrSlice(0, buf.capacity());
|
|
try testing.expect(buf.full);
|
|
try testing.expectEqual(@as(usize, 4), buf.len());
|
|
|
|
// Delete
|
|
buf.deleteOldest(2);
|
|
try testing.expect(!buf.full);
|
|
try testing.expectEqual(@as(usize, 2), buf.len());
|
|
|
|
try buf.appendSlice("AB");
|
|
{
|
|
var it = buf.iterator(.forward);
|
|
try testing.expect(it.next().?.* == 0);
|
|
try testing.expect(it.next().?.* == 0);
|
|
try testing.expect(it.next().?.* == 'A');
|
|
try testing.expect(it.next().?.* == 'B');
|
|
try testing.expect(it.next() == null);
|
|
}
|
|
}
|
|
|
|
test "CircBuf getPtrSlice fits" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 12);
|
|
defer buf.deinit(alloc);
|
|
|
|
const slices = buf.getPtrSlice(0, 11);
|
|
try testing.expectEqual(@as(usize, 11), slices[0].len);
|
|
try testing.expectEqual(@as(usize, 0), slices[1].len);
|
|
try testing.expectEqual(@as(usize, 11), buf.len());
|
|
}
|
|
|
|
test "CircBuf getPtrSlice wraps" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill the buffer
|
|
_ = buf.getPtrSlice(0, buf.capacity());
|
|
try testing.expect(buf.full);
|
|
try testing.expectEqual(@as(usize, 4), buf.len());
|
|
|
|
// Delete
|
|
buf.deleteOldest(2);
|
|
try testing.expect(!buf.full);
|
|
try testing.expectEqual(@as(usize, 2), buf.len());
|
|
|
|
// Get a slice that doesn't grow
|
|
{
|
|
const slices = buf.getPtrSlice(0, 2);
|
|
try testing.expectEqual(@as(usize, 2), slices[0].len);
|
|
try testing.expectEqual(@as(usize, 0), slices[1].len);
|
|
try testing.expectEqual(@as(usize, 2), buf.len());
|
|
slices[0][0] = 1;
|
|
slices[0][1] = 2;
|
|
}
|
|
|
|
// Get a slice that does grow, and forces wrap
|
|
{
|
|
const slices = buf.getPtrSlice(2, 2);
|
|
try testing.expectEqual(@as(usize, 2), slices[0].len);
|
|
try testing.expectEqual(@as(usize, 0), slices[1].len);
|
|
try testing.expectEqual(@as(usize, 4), buf.len());
|
|
|
|
// should be empty
|
|
try testing.expectEqual(@as(u8, 0), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 0), slices[0][1]);
|
|
slices[0][0] = 3;
|
|
slices[0][1] = 4;
|
|
}
|
|
|
|
// Get a slice across boundaries
|
|
{
|
|
const slices = buf.getPtrSlice(0, 4);
|
|
try testing.expectEqual(@as(usize, 2), slices[0].len);
|
|
try testing.expectEqual(@as(usize, 2), slices[1].len);
|
|
try testing.expectEqual(@as(usize, 4), buf.len());
|
|
|
|
try testing.expectEqual(@as(u8, 1), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 2), slices[0][1]);
|
|
try testing.expectEqual(@as(u8, 3), slices[1][0]);
|
|
try testing.expectEqual(@as(u8, 4), slices[1][1]);
|
|
}
|
|
}
|
|
|
|
test "CircBuf rotateToZero" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 12);
|
|
defer buf.deinit(alloc);
|
|
|
|
_ = buf.getPtrSlice(0, 11);
|
|
try buf.rotateToZero(alloc);
|
|
}
|
|
|
|
test "CircBuf rotateToZero offset" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill the buffer
|
|
_ = buf.getPtrSlice(0, 3);
|
|
try testing.expectEqual(@as(usize, 3), buf.len());
|
|
|
|
// Delete
|
|
buf.deleteOldest(2);
|
|
try testing.expect(!buf.full);
|
|
try testing.expectEqual(@as(usize, 1), buf.len());
|
|
try testing.expect(buf.tail > 0 and buf.head >= buf.tail);
|
|
|
|
// Rotate to zero
|
|
try buf.rotateToZero(alloc);
|
|
try testing.expectEqual(@as(usize, 0), buf.tail);
|
|
try testing.expectEqual(@as(usize, 1), buf.head);
|
|
}
|
|
|
|
test "CircBuf rotateToZero wraps" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill the buffer
|
|
_ = buf.getPtrSlice(0, 3);
|
|
try testing.expectEqual(@as(usize, 3), buf.len());
|
|
try testing.expect(buf.tail == 0 and buf.head == 3);
|
|
|
|
// Delete all
|
|
buf.deleteOldest(3);
|
|
try testing.expectEqual(@as(usize, 0), buf.len());
|
|
try testing.expect(buf.tail == 3 and buf.head == 3);
|
|
|
|
// Refill to force a wrap
|
|
{
|
|
const slices = buf.getPtrSlice(0, 3);
|
|
slices[0][0] = 1;
|
|
slices[1][0] = 2;
|
|
slices[1][1] = 3;
|
|
try testing.expectEqual(@as(usize, 3), buf.len());
|
|
try testing.expect(buf.tail == 3 and buf.head == 2);
|
|
}
|
|
|
|
// Rotate to zero
|
|
try buf.rotateToZero(alloc);
|
|
try testing.expectEqual(@as(usize, 0), buf.tail);
|
|
try testing.expectEqual(@as(usize, 3), buf.head);
|
|
{
|
|
const slices = buf.getPtrSlice(0, 3);
|
|
try testing.expectEqual(@as(u8, 1), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 2), slices[0][1]);
|
|
try testing.expectEqual(@as(u8, 3), slices[0][2]);
|
|
}
|
|
}
|
|
|
|
test "CircBuf rotateToZero full no wrap" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill the buffer
|
|
_ = buf.getPtrSlice(0, 3);
|
|
|
|
// Delete all
|
|
buf.deleteOldest(3);
|
|
|
|
// Refill to force a wrap
|
|
{
|
|
const slices = buf.getPtrSlice(0, 4);
|
|
try testing.expect(buf.full);
|
|
slices[0][0] = 1;
|
|
slices[1][0] = 2;
|
|
slices[1][1] = 3;
|
|
slices[1][2] = 4;
|
|
}
|
|
|
|
// Rotate to zero
|
|
try buf.rotateToZero(alloc);
|
|
try testing.expect(buf.full);
|
|
try testing.expectEqual(@as(usize, 0), buf.tail);
|
|
try testing.expectEqual(@as(usize, 0), buf.head);
|
|
{
|
|
const slices = buf.getPtrSlice(0, 4);
|
|
try testing.expectEqual(@as(u8, 1), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 2), slices[0][1]);
|
|
try testing.expectEqual(@as(u8, 3), slices[0][2]);
|
|
try testing.expectEqual(@as(u8, 4), slices[0][3]);
|
|
}
|
|
}
|
|
|
|
test "CircBuf resize grow from zero" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 0);
|
|
defer buf.deinit(alloc);
|
|
try testing.expect(buf.full);
|
|
|
|
// Resize
|
|
try buf.resize(alloc, 2);
|
|
try testing.expect(!buf.full);
|
|
try testing.expectEqual(@as(usize, 0), buf.len());
|
|
try testing.expectEqual(@as(usize, 2), buf.capacity());
|
|
|
|
try buf.append(1);
|
|
try buf.append(2);
|
|
|
|
{
|
|
const slices = buf.getPtrSlice(0, 2);
|
|
try testing.expectEqual(@as(u8, 1), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 2), slices[0][1]);
|
|
}
|
|
}
|
|
|
|
test "CircBuf resize grow" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill and write
|
|
{
|
|
const slices = buf.getPtrSlice(0, 4);
|
|
try testing.expect(buf.full);
|
|
slices[0][0] = 1;
|
|
slices[0][1] = 2;
|
|
slices[0][2] = 3;
|
|
slices[0][3] = 4;
|
|
}
|
|
|
|
// Resize
|
|
try buf.resize(alloc, 6);
|
|
try testing.expect(!buf.full);
|
|
try testing.expectEqual(@as(usize, 4), buf.len());
|
|
try testing.expectEqual(@as(usize, 6), buf.capacity());
|
|
|
|
{
|
|
const slices = buf.getPtrSlice(0, 4);
|
|
try testing.expectEqual(@as(u8, 1), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 2), slices[0][1]);
|
|
try testing.expectEqual(@as(u8, 3), slices[0][2]);
|
|
try testing.expectEqual(@as(u8, 4), slices[0][3]);
|
|
}
|
|
}
|
|
|
|
test "CircBuf resize shrink" {
|
|
const testing = std.testing;
|
|
const alloc = testing.allocator;
|
|
|
|
const Buf = CircBuf(u8, 0);
|
|
var buf = try Buf.init(alloc, 4);
|
|
defer buf.deinit(alloc);
|
|
|
|
// Fill and write
|
|
{
|
|
const slices = buf.getPtrSlice(0, 4);
|
|
try testing.expect(buf.full);
|
|
slices[0][0] = 1;
|
|
slices[0][1] = 2;
|
|
slices[0][2] = 3;
|
|
slices[0][3] = 4;
|
|
}
|
|
|
|
// Resize
|
|
try buf.resize(alloc, 3);
|
|
try testing.expect(buf.full);
|
|
try testing.expectEqual(@as(usize, 3), buf.len());
|
|
try testing.expectEqual(@as(usize, 3), buf.capacity());
|
|
|
|
{
|
|
const slices = buf.getPtrSlice(0, 3);
|
|
try testing.expectEqual(@as(u8, 1), slices[0][0]);
|
|
try testing.expectEqual(@as(u8, 2), slices[0][1]);
|
|
try testing.expectEqual(@as(u8, 3), slices[0][2]);
|
|
}
|
|
}
|