vtcode_core/ui/
stream_buffer.rs1use crate::ui::tui::{InlineMessageKind, InlineSegment};
7
8#[derive(Clone, Debug)]
10pub struct StreamConfig {
11 pub batch_size: usize,
13 pub max_buffer_bytes: usize,
15}
16
17impl Default for StreamConfig {
18 fn default() -> Self {
19 Self {
20 batch_size: 20, max_buffer_bytes: 65536, }
23 }
24}
25
26#[derive(Debug)]
28pub struct StreamBuffer {
29 lines: Vec<Vec<InlineSegment>>,
31 config: StreamConfig,
33 approximate_size: usize,
35}
36
37impl StreamBuffer {
38 pub fn new() -> Self {
40 Self::with_config(StreamConfig::default())
41 }
42
43 pub fn with_config(config: StreamConfig) -> Self {
45 Self {
46 lines: Vec::with_capacity(config.batch_size),
47 config,
48 approximate_size: 0,
49 }
50 }
51
52 pub fn append_line(&mut self, segments: Vec<InlineSegment>) -> bool {
54 let line_size: usize = segments.iter().map(|s| s.text.len()).sum();
56 self.approximate_size += line_size;
57 self.lines.push(segments);
58
59 self.should_flush()
61 }
62
63 fn should_flush(&self) -> bool {
65 self.lines.len() >= self.config.batch_size
66 || self.approximate_size >= self.config.max_buffer_bytes
67 }
68
69 pub fn flush(&mut self) -> Vec<Vec<InlineSegment>> {
71 self.approximate_size = 0;
72 std::mem::take(&mut self.lines)
73 }
74
75 pub fn len(&self) -> usize {
77 self.lines.len()
78 }
79
80 pub fn is_empty(&self) -> bool {
82 self.lines.is_empty()
83 }
84
85 pub fn approximate_bytes(&self) -> usize {
87 self.approximate_size
88 }
89
90 pub fn force_flush(&mut self) -> Vec<Vec<InlineSegment>> {
92 self.flush()
93 }
94
95 pub fn clear(&mut self) {
97 self.lines.clear();
98 self.approximate_size = 0;
99 }
100}
101
102impl Default for StreamBuffer {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108#[derive(Debug)]
110pub struct StreamingContext {
111 pub kind: InlineMessageKind,
113 pub buffer: StreamBuffer,
115 pub total_lines: usize,
117}
118
119impl StreamingContext {
120 pub fn new(kind: InlineMessageKind) -> Self {
122 Self {
123 kind,
124 buffer: StreamBuffer::new(),
125 total_lines: 0,
126 }
127 }
128
129 pub fn with_config(kind: InlineMessageKind, config: StreamConfig) -> Self {
131 Self {
132 kind,
133 buffer: StreamBuffer::with_config(config),
134 total_lines: 0,
135 }
136 }
137
138 pub fn append(&mut self, segments: Vec<InlineSegment>) -> bool {
140 let should_flush = self.buffer.append_line(segments);
141 self.total_lines += 1;
142 should_flush
143 }
144
145 pub fn flush(&mut self) -> Vec<Vec<InlineSegment>> {
147 self.buffer.flush()
148 }
149}
150
151pub struct AllocationPredictor {
153 bytes_per_line: usize,
155}
156
157impl AllocationPredictor {
158 pub fn new() -> Self {
160 Self {
161 bytes_per_line: 120, }
163 }
164
165 pub fn estimate_total_bytes(&self, line_count: usize) -> usize {
167 line_count * self.bytes_per_line
168 }
169
170 pub fn optimal_batch_size(&self, _total_bytes: usize) -> usize {
172 let target_batch_bytes = 8192;
174 let batch_lines = (target_batch_bytes / self.bytes_per_line).max(5);
175 batch_lines.min(50) }
177
178 pub fn pre_allocation_capacity(&self, estimated_lines: usize) -> usize {
180 (estimated_lines as f64 * 1.2) as usize }
182}
183
184impl Default for AllocationPredictor {
185 fn default() -> Self {
186 Self::new()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_stream_buffer_creation() {
196 let buffer = StreamBuffer::new();
197 assert!(buffer.is_empty());
198 assert_eq!(buffer.len(), 0);
199 }
200
201 #[test]
202 fn test_stream_buffer_append() {
203 let mut buffer = StreamBuffer::new();
204 let segment = InlineSegment {
205 text: "test".to_string(),
206 style: std::sync::Arc::new(Default::default()),
207 };
208 let should_flush = buffer.append_line(vec![segment]);
209 assert!(!should_flush); assert_eq!(buffer.len(), 1);
211 }
212
213 #[test]
214 fn test_stream_buffer_batch_flush() {
215 let mut buffer = StreamBuffer::with_config(StreamConfig {
216 batch_size: 5,
217 max_buffer_bytes: usize::MAX,
218 });
219
220 for i in 0..5 {
221 let segment = InlineSegment {
222 text: format!("line {}", i),
223 style: std::sync::Arc::new(Default::default()),
224 };
225 let should_flush = buffer.append_line(vec![segment]);
226 if i < 4 {
227 assert!(!should_flush);
228 } else {
229 assert!(should_flush);
230 }
231 }
232 assert_eq!(buffer.len(), 5);
233 }
234
235 #[test]
236 fn test_stream_buffer_byte_limit_flush() {
237 let mut buffer = StreamBuffer::with_config(StreamConfig {
238 batch_size: 100,
239 max_buffer_bytes: 50,
240 });
241
242 let segment = InlineSegment {
243 text: "x".repeat(60),
244 style: std::sync::Arc::new(Default::default()),
245 };
246 let should_flush = buffer.append_line(vec![segment]);
247 assert!(should_flush);
248 }
249
250 #[test]
251 fn test_stream_buffer_flush_returns_lines() {
252 let mut buffer = StreamBuffer::new();
253 let segment = InlineSegment {
254 text: "test".to_string(),
255 style: std::sync::Arc::new(Default::default()),
256 };
257 buffer.append_line(vec![segment]);
258
259 let flushed = buffer.flush();
260 assert_eq!(flushed.len(), 1);
261 assert!(buffer.is_empty());
262 }
263
264 #[test]
265 fn test_streaming_context() {
266 let mut ctx = StreamingContext::new(InlineMessageKind::Agent);
267 assert_eq!(ctx.total_lines, 0);
268
269 let segment = InlineSegment {
270 text: "test".to_string(),
271 style: std::sync::Arc::new(Default::default()),
272 };
273 ctx.append(vec![segment]);
274 assert_eq!(ctx.total_lines, 1);
275 }
276
277 #[test]
278 fn test_allocation_predictor() {
279 let predictor = AllocationPredictor::new();
280 let estimate = predictor.estimate_total_bytes(100);
281 assert!(estimate > 0);
282
283 let batch = predictor.optimal_batch_size(10000);
284 assert!(batch > 0 && batch <= 50);
285 }
286
287 #[test]
288 fn test_stream_config_defaults() {
289 let config = StreamConfig::default();
290 assert_eq!(config.batch_size, 20);
291 assert_eq!(config.max_buffer_bytes, 65536);
292 }
293
294 #[test]
295 fn test_pre_allocation_capacity() {
296 let predictor = AllocationPredictor::new();
297 let capacity = predictor.pre_allocation_capacity(100);
298 assert!(capacity >= 100); assert!(capacity <= 120); }
301}