Skip to main content

oxihuman_core/
chain_buffer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! A chain of fixed-size buffers for sequential data storage.
6
7/// One link in the chain.
8#[allow(dead_code)]
9#[derive(Debug, Clone)]
10struct ChainLink {
11    data: Vec<u8>,
12    used: usize,
13}
14
15/// A chain of fixed-size byte buffers.
16#[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"); // 10 bytes, 4 per link -> 3 links
130        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"); // 3 used of 8
174        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}