Files
ghostty-rs/crates/ghostty-datastruct/src/circ_buf.rs
T
ddidderr 5b8c92b98c feat(unicode,datastruct): port Unicode tables and data structures
ghostty-unicode: Ghostty computes codepoint properties with the uucode
Zig package (v0.2.0, Unicode 17.0.0). There is no Rust equivalent, so
a trimmed UCD set is vendored in ucd/ and build.rs re-implements
uucode's derivations: cell width rules incl. Ghostty's overrides for
emoji modifiers and Prepend, grapheme break classes with the
compile-time grapheme_break state table, the is_symbol set, and emoji
presentation. Lookups are three array indexes per codepoint. Validated
against the official GraphemeBreakTest.txt.

ghostty-datastruct: split_tree (used for GTK splits; f16 ratios so
rounding and debug diagrams match Zig), circ_buf, lru, cache_table,
blocking_queue, segmented_pool, message_data, comparison,
array_list_collection. intrusive_linked_list is not needed (lru is
index based). lru now reports the evicted (old) key, Zig reported the
new key, which appears to be a bug.

Not yet covered: case_folding_full (needed by input bindings).

Test Plan:
- cargo test -p ghostty-unicode (16) -p ghostty-datastruct (88)
- cargo clippy for both crates with -D warnings
2026-10-11 21:54:57 +02:00

790 lines
22 KiB
Rust

//! A circular buffer (`circ_buf.zig`).
use crate::OutOfMemory;
/// Direction of a [`Iter`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Forward,
Reverse,
}
/// A circular buffer containing type `T`, filled with a `default` value.
#[derive(Debug, Clone)]
pub struct CircBuf<T> {
// 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.
pub storage: Vec<T>,
pub head: usize,
pub 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.
pub full: bool,
default: T,
}
/// Iterator over the circular buffer.
#[derive(Debug, Clone)]
pub struct Iter<'a, T> {
buf: &'a CircBuf<T>,
idx: usize,
direction: Direction,
}
impl<T> Iter<'_, T> {
/// 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 seek_by(&mut self, amount: isize) {
if amount > 0 {
self.idx = self.idx.saturating_add(amount as usize);
} else {
self.idx = self.idx.saturating_sub(amount.unsigned_abs());
}
}
/// Reset the iterator back to the first value.
pub fn reset(&mut self) {
self.idx = 0;
}
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> {
if self.idx >= self.buf.len() {
return None;
}
// Get our index from the tail
let tail_idx = match self.direction {
Direction::Forward => self.idx,
Direction::Reverse => self.buf.len() - self.idx - 1,
};
// Translate the tail index to a storage index
let storage_idx = (self.buf.tail + tail_idx) % self.buf.capacity();
self.idx += 1;
Some(&self.buf.storage[storage_idx])
}
}
impl<T> CircBuf<T> {
/// Returns if the buffer is currently empty. To check if its
/// full, just check the "full" attribute.
#[must_use]
pub fn empty(&self) -> bool {
!self.full && self.head == self.tail
}
/// Returns the total capacity allocated for this buffer.
#[must_use]
pub fn capacity(&self) -> usize {
self.storage.len()
}
/// Returns the length in elements that are used.
#[must_use]
pub fn len(&self) -> usize {
if self.full {
return self.storage.len();
}
if self.head >= self.tail {
return self.head - self.tail;
}
self.storage.len() - (self.tail - self.head)
}
/// Alias for [`empty`](Self::empty) (the clippy-conventional name).
#[must_use]
pub fn is_empty(&self) -> bool {
self.empty()
}
}
impl<T: Clone> CircBuf<T> {
/// Initialize a new circular buffer that can store `size` elements.
#[must_use]
pub fn new(size: usize, default: T) -> Self {
Self {
storage: vec![default.clone(); size],
head: 0,
tail: 0,
full: size == 0,
default,
}
}
/// Append a single value to the buffer. If the buffer is full,
/// an error will be returned.
pub fn append(&mut self, v: T) -> Result<(), OutOfMemory> {
if self.full {
return Err(OutOfMemory);
}
self.storage[self.head] = v;
self.head += 1;
if self.head >= self.storage.len() {
self.head = 0;
}
self.full = self.head == self.tail;
Ok(())
}
/// Append a single value to the buffer, assuming there is capacity.
pub fn append_assume_capacity(&mut self, v: T) {
assert!(!self.full);
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.
pub fn append_slice_assume_capacity(&mut self, slice: &[T]) {
let len = self.len();
let [a, b] = self.get_ptr_slice(len, slice.len());
let a_len = a.len();
a.clone_from_slice(&slice[..a_len]);
b.clone_from_slice(&slice[a_len..]);
}
/// Clear the buffer.
pub fn clear(&mut self) {
self.head = 0;
self.tail = 0;
self.full = false;
}
/// Iterate over the circular buffer.
#[must_use]
pub fn iterator(&self, direction: Direction) -> Iter<'_, T> {
Iter {
buf: self,
idx: 0,
direction,
}
}
/// Get the first (oldest) value in the buffer.
#[must_use]
pub fn first(&self) -> Option<&T> {
self.iterator(Direction::Forward).next()
}
/// Get the last (newest) value in the buffer.
#[must_use]
pub fn last(&self) -> Option<&T> {
self.iterator(Direction::Reverse).next()
}
/// Mutable access to the first (oldest) value in the buffer.
pub fn first_mut(&mut self) -> Option<&mut T> {
if self.is_empty() {
return None;
}
let idx = self.tail % self.capacity();
Some(&mut self.storage[idx])
}
/// Mutable access to the last (newest) value in the buffer.
pub fn last_mut(&mut self) -> Option<&mut T> {
let len = self.len();
if len == 0 {
return None;
}
let idx = (self.tail + len - 1) % self.capacity();
Some(&mut self.storage[idx])
}
/// Ensures that there is enough capacity to store `amount` more
/// items via append.
pub fn ensure_unused_capacity(&mut self, amount: usize) {
let new_cap = self.len() + amount;
if new_cap <= self.capacity() {
return;
}
self.resize(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(&mut self, size: usize) {
// Rotate to zero so it is aligned.
self.rotate_to_zero();
// Reallocate, this adds to the end so we're ready to go.
let prev_len = self.len();
let prev_cap = self.storage.len();
self.storage.resize(size, self.default.clone());
// If we grew, we need to set our new defaults. We can add it
// at the end since we rotated to start. (Done by `resize` above.)
if size > prev_cap {
// Fix up our head/tail
if self.full {
self.head = prev_len;
self.full = false;
}
}
}
/// Rotate the data so that it is zero-aligned.
pub(crate) fn rotate_to_zero(&mut self) {
// If we're already at zero then do nothing.
if self.tail == 0 {
return;
}
// Rotate our storage in-place.
self.storage.rotate_left(self.tail);
// Then fix up our head and tail.
self.head = self.len() % self.storage.len();
self.tail = 0;
}
/// Delete the oldest n values from the buffer. If there are less
/// than n values in the buffer, it'll delete everything.
pub fn delete_oldest(&mut self, n: usize) {
assert!(n <= self.storage.len());
// Special case n == 0 otherwise we will accidentally break
// our circular buffer.
if n == 0 {
return;
}
// Clear the values back to default
let default = self.default.clone();
let [a, b] = self.get_ptr_slice(0, n);
a.fill(default.clone());
b.fill(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 += self.len().min(n);
if self.tail >= self.storage.len() {
self.tail -= self.storage.len();
}
self.full = false;
}
/// Returns the two (possibly empty) slices covering `slice_len` values at
/// `offset`, 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 get_ptr_slice(&mut self, offset: usize, slice_len: usize) -> [&mut [T]; 2] {
// Special case the empty slice fast-path.
if slice_len == 0 {
return [&mut [], &mut []];
}
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.
let 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
let start_idx = self.storage_offset(offset);
let end_idx = self.storage_offset(end_offset - 1);
// Optimistically, our data fits in one slice
if end_idx >= start_idx {
return [&mut self.storage[start_idx..=end_idx], &mut []];
}
let (front, back) = self.storage.split_at_mut(start_idx);
[back, &mut front[..=end_idx]]
}
/// Advances the head/tail so that we can store amount.
fn advance(&mut self, amount: usize) {
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.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 storage_offset(&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.
let fits_offset = self.tail + offset;
if fits_offset < self.storage.len() {
return fits_offset;
}
fits_offset - self.storage.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn collect(buf: &CircBuf<u8>, d: Direction) -> Vec<u8> {
buf.iterator(d).copied().collect()
}
#[test]
fn circ_buf_basic() {
let buf = CircBuf::new(12, 0u8);
assert!(buf.empty());
assert_eq!(buf.len(), 0);
}
#[test]
fn circ_buf_append() {
let mut buf = CircBuf::new(3, 0u8);
buf.append(1).unwrap();
buf.append(2).unwrap();
buf.append(3).unwrap();
assert_eq!(buf.append(4), Err(OutOfMemory));
buf.delete_oldest(1);
buf.append(4).unwrap();
assert_eq!(buf.append(5), Err(OutOfMemory));
}
#[test]
fn circ_buf_forward_iterator() {
let mut buf = CircBuf::new(3, 0u8);
// Empty
assert!(buf.iterator(Direction::Forward).next().is_none());
// Partially full
buf.append(1).unwrap();
buf.append(2).unwrap();
assert_eq!(collect(&buf, Direction::Forward), [1, 2]);
// Full
buf.append(3).unwrap();
assert_eq!(collect(&buf, Direction::Forward), [1, 2, 3]);
// Delete and add
buf.delete_oldest(1);
buf.append(4).unwrap();
assert_eq!(collect(&buf, Direction::Forward), [2, 3, 4]);
}
#[test]
fn circ_buf_reverse_iterator() {
let mut buf = CircBuf::new(3, 0u8);
// Empty
assert!(buf.iterator(Direction::Reverse).next().is_none());
// Partially full
buf.append(1).unwrap();
buf.append(2).unwrap();
assert_eq!(collect(&buf, Direction::Reverse), [2, 1]);
// Full
buf.append(3).unwrap();
assert_eq!(collect(&buf, Direction::Reverse), [3, 2, 1]);
// Delete and add
buf.delete_oldest(1);
buf.append(4).unwrap();
assert_eq!(collect(&buf, Direction::Reverse), [4, 3, 2]);
}
#[test]
fn circ_buf_iterator_seek() {
let mut buf = CircBuf::new(4, 0u8);
buf.append_slice_assume_capacity(&[1, 2, 3, 4]);
let mut it = buf.iterator(Direction::Forward);
it.seek_by(2);
assert_eq!(it.next(), Some(&3));
it.seek_by(-100);
assert_eq!(it.next(), Some(&1));
it.seek_by(isize::MAX);
assert_eq!(it.next(), None);
it.reset();
assert_eq!(it.next(), Some(&1));
}
#[test]
fn circ_buf_first_last() {
let mut buf = CircBuf::new(3, 0u8);
buf.append(1).unwrap();
buf.append(2).unwrap();
buf.append(3).unwrap();
assert_eq!(buf.last(), Some(&3));
assert_eq!(buf.first(), Some(&1));
*buf.last_mut().unwrap() = 9;
*buf.first_mut().unwrap() = 7;
assert_eq!(collect(&buf, Direction::Forward), [7, 2, 9]);
}
#[test]
fn circ_buf_first_last_empty() {
let buf = CircBuf::new(0, 0u8);
assert!(buf.first().is_none());
assert!(buf.last().is_none());
}
#[test]
fn circ_buf_first_last_empty_with_cap() {
let mut buf = CircBuf::new(3, 0u8);
assert!(buf.first().is_none());
assert!(buf.last().is_none());
assert!(buf.first_mut().is_none());
assert!(buf.last_mut().is_none());
}
#[test]
fn circ_buf_append_slice() {
let mut buf = CircBuf::new(5, 0u8);
buf.append_slice_assume_capacity(b"hello");
assert_eq!(collect(&buf, Direction::Forward), b"hello");
}
#[test]
fn circ_buf_append_slice_with_wrap() {
let mut buf = CircBuf::new(4, 0u8);
// Fill the buffer
let cap = buf.capacity();
let _ = buf.get_ptr_slice(0, cap);
assert!(buf.full);
assert_eq!(buf.len(), 4);
// Delete
buf.delete_oldest(2);
assert!(!buf.full);
assert_eq!(buf.len(), 2);
buf.append_slice_assume_capacity(b"AB");
assert_eq!(collect(&buf, Direction::Forward), [0, 0, b'A', b'B']);
}
#[test]
fn circ_buf_get_ptr_slice_fits() {
let mut buf = CircBuf::new(12, 0u8);
let slices = buf.get_ptr_slice(0, 11);
assert_eq!(slices[0].len(), 11);
assert_eq!(slices[1].len(), 0);
assert_eq!(buf.len(), 11);
}
#[test]
fn circ_buf_get_ptr_slice_wraps() {
let mut buf = CircBuf::new(4, 0u8);
// Fill the buffer
let cap = buf.capacity();
let _ = buf.get_ptr_slice(0, cap);
assert!(buf.full);
assert_eq!(buf.len(), 4);
// Delete
buf.delete_oldest(2);
assert!(!buf.full);
assert_eq!(buf.len(), 2);
// Get a slice that doesn't grow
{
let slices = buf.get_ptr_slice(0, 2);
assert_eq!(slices[0].len(), 2);
assert_eq!(slices[1].len(), 0);
slices[0][0] = 1;
slices[0][1] = 2;
}
assert_eq!(buf.len(), 2);
// Get a slice that does grow, and forces wrap
{
let slices = buf.get_ptr_slice(2, 2);
assert_eq!(slices[0].len(), 2);
assert_eq!(slices[1].len(), 0);
// should be empty
assert_eq!(slices[0][0], 0);
assert_eq!(slices[0][1], 0);
slices[0][0] = 3;
slices[0][1] = 4;
}
assert_eq!(buf.len(), 4);
// Get a slice across boundaries
{
let slices = buf.get_ptr_slice(0, 4);
assert_eq!(slices[0].len(), 2);
assert_eq!(slices[1].len(), 2);
assert_eq!(slices[0][0], 1);
assert_eq!(slices[0][1], 2);
assert_eq!(slices[1][0], 3);
assert_eq!(slices[1][1], 4);
}
assert_eq!(buf.len(), 4);
}
#[test]
fn circ_buf_rotate_to_zero() {
let mut buf = CircBuf::new(12, 0u8);
let _ = buf.get_ptr_slice(0, 11);
buf.rotate_to_zero();
}
#[test]
fn circ_buf_rotate_to_zero_offset() {
let mut buf = CircBuf::new(4, 0u8);
// Fill the buffer
let _ = buf.get_ptr_slice(0, 3);
assert_eq!(buf.len(), 3);
// Delete
buf.delete_oldest(2);
assert!(!buf.full);
assert_eq!(buf.len(), 1);
assert!(buf.tail > 0 && buf.head >= buf.tail);
// Rotate to zero
buf.rotate_to_zero();
assert_eq!(buf.tail, 0);
assert_eq!(buf.head, 1);
}
#[test]
fn circ_buf_rotate_to_zero_wraps() {
let mut buf = CircBuf::new(4, 0u8);
// Fill the buffer
let _ = buf.get_ptr_slice(0, 3);
assert_eq!(buf.len(), 3);
assert!(buf.tail == 0 && buf.head == 3);
// Delete all
buf.delete_oldest(3);
assert_eq!(buf.len(), 0);
assert!(buf.tail == 3 && buf.head == 3);
// Refill to force a wrap
{
let slices = buf.get_ptr_slice(0, 3);
slices[0][0] = 1;
slices[1][0] = 2;
slices[1][1] = 3;
}
assert_eq!(buf.len(), 3);
assert!(buf.tail == 3 && buf.head == 2);
// Rotate to zero
buf.rotate_to_zero();
assert_eq!(buf.tail, 0);
assert_eq!(buf.head, 3);
{
let slices = buf.get_ptr_slice(0, 3);
assert_eq!(slices[0][0], 1);
assert_eq!(slices[0][1], 2);
assert_eq!(slices[0][2], 3);
}
}
#[test]
fn circ_buf_rotate_to_zero_full_no_wrap() {
let mut buf = CircBuf::new(4, 0u8);
// Fill the buffer
let _ = buf.get_ptr_slice(0, 3);
// Delete all
buf.delete_oldest(3);
// Refill to force a wrap
{
let slices = buf.get_ptr_slice(0, 4);
slices[0][0] = 1;
slices[1][0] = 2;
slices[1][1] = 3;
slices[1][2] = 4;
}
assert!(buf.full);
// Rotate to zero
buf.rotate_to_zero();
assert!(buf.full);
assert_eq!(buf.tail, 0);
assert_eq!(buf.head, 0);
{
let slices = buf.get_ptr_slice(0, 4);
assert_eq!(slices[0], [1, 2, 3, 4]);
}
}
#[test]
fn circ_buf_resize_grow_from_zero() {
let mut buf = CircBuf::new(0, 0u8);
assert!(buf.full);
// Resize
buf.resize(2);
assert!(!buf.full);
assert_eq!(buf.len(), 0);
assert_eq!(buf.capacity(), 2);
buf.append(1).unwrap();
buf.append(2).unwrap();
let slices = buf.get_ptr_slice(0, 2);
assert_eq!(slices[0][0], 1);
assert_eq!(slices[0][1], 2);
}
#[test]
fn circ_buf_resize_grow() {
let mut buf = CircBuf::new(4, 0u8);
// Fill and write
{
let slices = buf.get_ptr_slice(0, 4);
slices[0].copy_from_slice(&[1, 2, 3, 4]);
}
assert!(buf.full);
// Resize
buf.resize(6);
assert!(!buf.full);
assert_eq!(buf.len(), 4);
assert_eq!(buf.capacity(), 6);
let slices = buf.get_ptr_slice(0, 4);
assert_eq!(slices[0], [1, 2, 3, 4]);
}
#[test]
fn circ_buf_resize_shrink() {
let mut buf = CircBuf::new(4, 0u8);
// Fill and write
{
let slices = buf.get_ptr_slice(0, 4);
slices[0].copy_from_slice(&[1, 2, 3, 4]);
}
assert!(buf.full);
// Resize
buf.resize(3);
assert!(buf.full);
assert_eq!(buf.len(), 3);
assert_eq!(buf.capacity(), 3);
let slices = buf.get_ptr_slice(0, 3);
assert_eq!(slices[0], [1, 2, 3]);
}
#[test]
fn circ_buf_append_empty_slice() {
let mut buf = CircBuf::new(5, 0u8);
// Appending an empty slice to empty buffer should be a no-op
buf.append_slice_assume_capacity(b"");
assert_eq!(buf.len(), 0);
assert!(!buf.full);
// Buffer should still work normally after appending empty slice
buf.append_slice_assume_capacity(b"hi");
assert_eq!(buf.len(), 2);
// Appending an empty slice to non-empty buffer should also be a no-op
buf.append_slice_assume_capacity(b"");
assert_eq!(buf.len(), 2);
}
#[test]
fn circ_buf_get_ptr_slice_zero_length() {
let mut buf = CircBuf::new(5, 0u8);
// zero length on empty buffer should return empty slices
let slices = buf.get_ptr_slice(0, 0);
assert_eq!(slices[0].len(), 0);
assert_eq!(slices[1].len(), 0);
assert_eq!(buf.len(), 0);
// Fill buffer partially
buf.append_slice_assume_capacity(b"abc");
assert_eq!(buf.len(), 3);
// zero length on non-empty buffer should also work
let slices2 = buf.get_ptr_slice(0, 0);
assert_eq!(slices2[0].len(), 0);
assert_eq!(slices2[1].len(), 0);
assert_eq!(buf.len(), 3);
}
#[test]
fn circ_buf_delete_oldest_zero() {
let mut buf = CircBuf::new(5, 0u8);
// delete_oldest(0) on empty buffer should be a no-op
buf.delete_oldest(0);
assert_eq!(buf.len(), 0);
// Fill buffer
buf.append_slice_assume_capacity(b"hello");
assert_eq!(buf.len(), 5);
// delete_oldest(0) on non-empty buffer should be a no-op
buf.delete_oldest(0);
assert_eq!(buf.len(), 5);
// Verify data is unchanged
assert_eq!(buf.iterator(Direction::Forward).next(), Some(&b'h'));
}
#[test]
fn circ_buf_ensure_unused_capacity() {
let mut buf = CircBuf::new(2, 0u8);
buf.append(1).unwrap();
buf.ensure_unused_capacity(1);
assert_eq!(buf.capacity(), 2);
buf.ensure_unused_capacity(5);
assert_eq!(buf.capacity(), 6);
assert_eq!(collect(&buf, Direction::Forward), [1]);
}
}