oxihuman_core/
chain_buffer.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10struct ChainLink {
11 data: Vec<u8>,
12 used: usize,
13}
14
15#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct ChainBuffer {
19 links: Vec<ChainLink>,
20 link_capacity: usize,
21}
22
23#[allow(dead_code)]
24impl ChainBuffer {
25 pub fn new(link_capacity: usize) -> Self {
26 let cap = if link_capacity == 0 {
27 64
28 } else {
29 link_capacity
30 };
31 Self {
32 links: Vec::new(),
33 link_capacity: cap,
34 }
35 }
36
37 pub fn write(&mut self, data: &[u8]) {
38 let mut offset = 0;
39 while offset < data.len() {
40 if self.links.is_empty()
41 || self
42 .links
43 .last()
44 .is_none_or(|l| l.used >= self.link_capacity)
45 {
46 self.links.push(ChainLink {
47 data: vec![0u8; self.link_capacity],
48 used: 0,
49 });
50 }
51 let Some(link) = self.links.last_mut() else {
52 break;
53 };
54 let space = self.link_capacity - link.used;
55 let to_copy = space.min(data.len() - offset);
56 link.data[link.used..link.used + to_copy]
57 .copy_from_slice(&data[offset..offset + to_copy]);
58 link.used += to_copy;
59 offset += to_copy;
60 }
61 }
62
63 pub fn total_bytes(&self) -> usize {
64 self.links.iter().map(|l| l.used).sum()
65 }
66
67 pub fn link_count(&self) -> usize {
68 self.links.len()
69 }
70
71 pub fn link_capacity(&self) -> usize {
72 self.link_capacity
73 }
74
75 pub fn is_empty(&self) -> bool {
76 self.links.is_empty()
77 }
78
79 pub fn to_vec(&self) -> Vec<u8> {
80 let mut out = Vec::with_capacity(self.total_bytes());
81 for link in &self.links {
82 out.extend_from_slice(&link.data[..link.used]);
83 }
84 out
85 }
86
87 pub fn clear(&mut self) {
88 self.links.clear();
89 }
90
91 pub fn read_at(&self, offset: usize) -> Option<u8> {
92 let mut remaining = offset;
93 for link in &self.links {
94 if remaining < link.used {
95 return Some(link.data[remaining]);
96 }
97 remaining -= link.used;
98 }
99 None
100 }
101
102 pub fn wasted_bytes(&self) -> usize {
103 self.links.iter().map(|l| self.link_capacity - l.used).sum()
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn new_chain_is_empty() {
113 let c = ChainBuffer::new(64);
114 assert!(c.is_empty());
115 assert_eq!(c.total_bytes(), 0);
116 }
117
118 #[test]
119 fn write_small_data() {
120 let mut c = ChainBuffer::new(16);
121 c.write(b"hello");
122 assert_eq!(c.total_bytes(), 5);
123 assert_eq!(c.link_count(), 1);
124 }
125
126 #[test]
127 fn write_spans_multiple_links() {
128 let mut c = ChainBuffer::new(4);
129 c.write(b"abcdefghij"); assert_eq!(c.link_count(), 3);
131 assert_eq!(c.total_bytes(), 10);
132 }
133
134 #[test]
135 fn to_vec_reconstructs_data() {
136 let mut c = ChainBuffer::new(3);
137 c.write(b"hello world");
138 assert_eq!(c.to_vec(), b"hello world");
139 }
140
141 #[test]
142 fn read_at_returns_byte() {
143 let mut c = ChainBuffer::new(4);
144 c.write(b"abcdef");
145 assert_eq!(c.read_at(0), Some(b'a'));
146 assert_eq!(c.read_at(4), Some(b'e'));
147 assert_eq!(c.read_at(10), None);
148 }
149
150 #[test]
151 fn clear_empties() {
152 let mut c = ChainBuffer::new(8);
153 c.write(b"data");
154 c.clear();
155 assert!(c.is_empty());
156 }
157
158 #[test]
159 fn link_capacity_returns_configured() {
160 let c = ChainBuffer::new(128);
161 assert_eq!(c.link_capacity(), 128);
162 }
163
164 #[test]
165 fn zero_capacity_defaults() {
166 let c = ChainBuffer::new(0);
167 assert_eq!(c.link_capacity(), 64);
168 }
169
170 #[test]
171 fn wasted_bytes_calculated() {
172 let mut c = ChainBuffer::new(8);
173 c.write(b"abc"); assert_eq!(c.wasted_bytes(), 5);
175 }
176
177 #[test]
178 fn multiple_writes_append() {
179 let mut c = ChainBuffer::new(16);
180 c.write(b"foo");
181 c.write(b"bar");
182 assert_eq!(c.to_vec(), b"foobar");
183 }
184}