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 || self.approximate_size >= self.config.max_buffer_bytes
66 }
67
68 pub fn flush(&mut self) -> Vec<Vec<InlineSegment>> {
70 self.approximate_size = 0;
71 std::mem::take(&mut self.lines)
72 }
73
74 pub fn len(&self) -> usize {
76 self.lines.len()
77 }
78
79 pub fn is_empty(&self) -> bool {
81 self.lines.is_empty()
82 }
83
84 pub fn approximate_bytes(&self) -> usize {
86 self.approximate_size
87 }
88
89 pub fn force_flush(&mut self) -> Vec<Vec<InlineSegment>> {
91 self.flush()
92 }
93
94 pub fn clear(&mut self) {
96 self.lines.clear();
97 self.approximate_size = 0;
98 }
99}
100
101impl Default for StreamBuffer {
102 fn default() -> Self {
103 Self::new()
104 }
105}
106
107#[derive(Debug)]
109pub struct StreamingContext {
110 pub kind: InlineMessageKind,
112 pub buffer: StreamBuffer,
114 pub total_lines: usize,
116}
117
118impl StreamingContext {
119 pub fn new(kind: InlineMessageKind) -> Self {
121 Self { kind, buffer: StreamBuffer::new(), total_lines: 0 }
122 }
123
124 pub fn with_config(kind: InlineMessageKind, config: StreamConfig) -> Self {
126 Self {
127 kind,
128 buffer: StreamBuffer::with_config(config),
129 total_lines: 0,
130 }
131 }
132
133 pub fn append(&mut self, segments: Vec<InlineSegment>) -> bool {
135 let should_flush = self.buffer.append_line(segments);
136 self.total_lines += 1;
137 should_flush
138 }
139
140 pub fn flush(&mut self) -> Vec<Vec<InlineSegment>> {
142 self.buffer.flush()
143 }
144}
145
146pub struct AllocationPredictor {
148 bytes_per_line: usize,
150}
151
152impl AllocationPredictor {
153 pub fn new() -> Self {
155 Self {
156 bytes_per_line: 120, }
158 }
159
160 pub fn estimate_total_bytes(&self, line_count: usize) -> usize {
162 line_count * self.bytes_per_line
163 }
164
165 pub fn optimal_batch_size(&self, _total_bytes: usize) -> usize {
167 let target_batch_bytes = 8192;
169 let batch_lines = (target_batch_bytes / self.bytes_per_line).max(5);
170 batch_lines.min(50) }
172
173 pub fn pre_allocation_capacity(&self, estimated_lines: usize) -> usize {
175 #[allow(
176 clippy::cast_sign_loss,
177 clippy::let_and_return,
178 reason = "Intentional compatibility, platform, or test-only suppression."
179 )]
180 let cap = ((estimated_lines as f64 * 1.2).max(0.0)) as usize; cap
182 }
183}
184
185impl Default for AllocationPredictor {
186 fn default() -> Self {
187 Self::new()
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_stream_buffer_creation() {
197 let buffer = StreamBuffer::new();
198 assert!(buffer.is_empty());
199 assert_eq!(buffer.len(), 0);
200 }
201
202 #[test]
203 fn test_stream_buffer_append() {
204 let mut buffer = StreamBuffer::new();
205 let segment = InlineSegment {
206 text: "test".to_string(),
207 style: std::sync::Arc::new(Default::default()),
208 };
209 let should_flush = buffer.append_line(vec![segment]);
210 assert!(!should_flush); assert_eq!(buffer.len(), 1);
212 }
213
214 #[test]
215 fn test_stream_buffer_batch_flush() {
216 let mut buffer = StreamBuffer::with_config(StreamConfig { batch_size: 5, max_buffer_bytes: usize::MAX });
217
218 for i in 0..5 {
219 let segment = InlineSegment {
220 text: format!("line {i}"),
221 style: std::sync::Arc::new(Default::default()),
222 };
223 let should_flush = buffer.append_line(vec![segment]);
224 if i < 4 {
225 assert!(!should_flush);
226 } else {
227 assert!(should_flush);
228 }
229 }
230 assert_eq!(buffer.len(), 5);
231 }
232
233 #[test]
234 fn test_stream_buffer_byte_limit_flush() {
235 let mut buffer = StreamBuffer::with_config(StreamConfig { batch_size: 100, max_buffer_bytes: 50 });
236
237 let segment = InlineSegment {
238 text: "x".repeat(60),
239 style: std::sync::Arc::new(Default::default()),
240 };
241 let should_flush = buffer.append_line(vec![segment]);
242 assert!(should_flush);
243 }
244
245 #[test]
246 fn test_stream_buffer_flush_returns_lines() {
247 let mut buffer = StreamBuffer::new();
248 let segment = InlineSegment {
249 text: "test".to_string(),
250 style: std::sync::Arc::new(Default::default()),
251 };
252 buffer.append_line(vec![segment]);
253
254 let flushed = buffer.flush();
255 assert_eq!(flushed.len(), 1);
256 assert!(buffer.is_empty());
257 }
258
259 #[test]
260 fn test_streaming_context() {
261 let mut ctx = StreamingContext::new(InlineMessageKind::Agent);
262 assert_eq!(ctx.total_lines, 0);
263
264 let segment = InlineSegment {
265 text: "test".to_string(),
266 style: std::sync::Arc::new(Default::default()),
267 };
268 ctx.append(vec![segment]);
269 assert_eq!(ctx.total_lines, 1);
270 }
271
272 #[test]
273 fn test_allocation_predictor() {
274 let predictor = AllocationPredictor::new();
275 let estimate = predictor.estimate_total_bytes(100);
276 assert!(estimate > 0);
277
278 let batch = predictor.optimal_batch_size(10000);
279 assert!(batch > 0 && batch <= 50);
280 }
281
282 #[test]
283 fn test_stream_config_defaults() {
284 let config = StreamConfig::default();
285 assert_eq!(config.batch_size, 20);
286 assert_eq!(config.max_buffer_bytes, 65536);
287 }
288
289 #[test]
290 fn test_pre_allocation_capacity() {
291 let predictor = AllocationPredictor::new();
292 let capacity = predictor.pre_allocation_capacity(100);
293 assert!(capacity >= 100); assert!(capacity <= 120); }
296}