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
This commit is contained in:
1 parent
e724645057
commit
3323c3f525
31 files changed
+18468
-3
No files matched your search
@@ -0,0 +1,147 @@
|
||||
//! A pool of values with stable slots (`segmented_pool.zig`).
|
||||
|
||||
/// Returned by [`SegmentedPool::get`] when there are no values available.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("out of values")]
|
||||
pub struct OutOfValues;
|
||||
|
||||
/// A data structure where you can get stable (never moved) slots for
|
||||
/// a type that automatically grows if necessary. The values can be "put back"
|
||||
/// but are expected to be put back IN ORDER.
|
||||
///
|
||||
/// This is implemented specifically for libuv write requests, since the
|
||||
/// write requests must have a stable pointer and are guaranteed to be processed
|
||||
/// in order for a single stream.
|
||||
///
|
||||
/// Instead of raw pointers, slots are identified by a stable index returned
|
||||
/// from [`get`](Self::get) and accessed with [`at`](Self::at) /
|
||||
/// [`at_mut`](Self::at_mut).
|
||||
///
|
||||
/// This is NOT thread safe.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SegmentedPool<T: Default, const PREALLOC: usize> {
|
||||
i: usize,
|
||||
available: usize,
|
||||
list: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Default, const PREALLOC: usize> Default for SegmentedPool<T, PREALLOC> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default, const PREALLOC: usize> SegmentedPool<T, PREALLOC> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
i: 0,
|
||||
available: PREALLOC,
|
||||
list: (0..PREALLOC).map(|_| T::default()).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of values currently available without growing.
|
||||
#[must_use]
|
||||
pub fn available(&self) -> usize {
|
||||
self.available
|
||||
}
|
||||
|
||||
/// Total number of slots.
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.list.len()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.list.is_empty()
|
||||
}
|
||||
|
||||
/// Access the slot at an index returned by `get`.
|
||||
#[must_use]
|
||||
pub fn at(&self, idx: usize) -> &T {
|
||||
&self.list[idx]
|
||||
}
|
||||
|
||||
/// Mutably access the slot at an index returned by `get`.
|
||||
pub fn at_mut(&mut self, idx: usize) -> &mut T {
|
||||
&mut self.list[idx]
|
||||
}
|
||||
|
||||
/// Get the next available value out of the list (as a slot index). This
|
||||
/// will not grow the list.
|
||||
pub fn get(&mut self) -> Result<usize, OutOfValues> {
|
||||
// Error to not have any
|
||||
if self.available == 0 {
|
||||
return Err(OutOfValues);
|
||||
}
|
||||
|
||||
// The index we grab is just i % len, so we wrap around to the front.
|
||||
let i = self.i % self.list.len();
|
||||
self.i = self.i.wrapping_add(1); // Wrapping addition so we go back to 0
|
||||
self.available -= 1;
|
||||
Ok(i)
|
||||
}
|
||||
|
||||
/// Get the next available value out of the list and grow the list
|
||||
/// if necessary.
|
||||
pub fn get_grow(&mut self) -> Result<usize, OutOfValues> {
|
||||
if self.available == 0 {
|
||||
self.grow();
|
||||
}
|
||||
self.get()
|
||||
}
|
||||
|
||||
fn grow(&mut self) {
|
||||
let old = self.list.len();
|
||||
self.list.extend((0..old).map(|_| T::default()));
|
||||
self.i = old;
|
||||
self.available = old;
|
||||
}
|
||||
|
||||
/// Put a value back. The value put back is expected to be the
|
||||
/// in order of get.
|
||||
pub fn put(&mut self) {
|
||||
self.available += 1;
|
||||
assert!(self.available <= self.list.len());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn segmented_pool() {
|
||||
let mut list: SegmentedPool<u8, 2> = SegmentedPool::new();
|
||||
assert_eq!(list.available(), 2);
|
||||
|
||||
// Get to capacity
|
||||
let v1 = list.get().unwrap();
|
||||
let v2 = list.get().unwrap();
|
||||
assert_ne!(v1, v2);
|
||||
assert_eq!(list.get(), Err(OutOfValues));
|
||||
|
||||
// Test writing for later
|
||||
*list.at_mut(v1) = 42;
|
||||
|
||||
// Put a value back
|
||||
list.put();
|
||||
let temp = list.get().unwrap();
|
||||
assert_eq!(v1, temp);
|
||||
assert_eq!(*list.at(temp), 42);
|
||||
assert_eq!(list.get(), Err(OutOfValues));
|
||||
|
||||
// Grow
|
||||
let v3 = list.get_grow().unwrap();
|
||||
assert!(v1 != v3 && v2 != v3);
|
||||
list.get().unwrap();
|
||||
assert_eq!(list.get(), Err(OutOfValues));
|
||||
|
||||
// Put a value back
|
||||
list.put();
|
||||
assert_eq!(v1, list.get().unwrap());
|
||||
assert_eq!(list.get(), Err(OutOfValues));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user