1#[derive(Debug, Clone)]
8pub struct EmbeddingBuffer {
9 pub buffer_id: u64,
11 pub data: Vec<f32>,
13 pub dim: usize,
15 pub in_use: bool,
17 pub generation: u64,
19}
20
21impl EmbeddingBuffer {
22 pub fn is_valid_dim(&self, expected: usize) -> bool {
24 self.dim == expected
25 }
26}
27
28#[derive(Debug, Clone)]
30pub struct PoolConfig {
31 pub embedding_dim: usize,
33 pub initial_capacity: usize,
35 pub max_capacity: usize,
37}
38
39impl PoolConfig {
40 pub fn default_for(dim: usize) -> Self {
42 Self {
43 embedding_dim: dim,
44 initial_capacity: 16,
45 max_capacity: 256,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Default)]
52pub struct PoolStats {
53 pub total_buffers: usize,
55 pub in_use: usize,
57 pub available: usize,
59 pub total_allocations: u64,
61 pub total_returns: u64,
63 pub cache_hits: u64,
65 pub cache_misses: u64,
67}
68
69pub struct SemanticEmbeddingPool {
75 pub buffers: Vec<EmbeddingBuffer>,
77 next_id: u64,
78 pub config: PoolConfig,
80 pub stats: PoolStats,
82}
83
84impl SemanticEmbeddingPool {
85 pub fn new(config: PoolConfig) -> Self {
87 let dim = config.embedding_dim;
88 let initial = config.initial_capacity;
89 let mut buffers = Vec::with_capacity(initial);
90 for id in 0..initial as u64 {
91 buffers.push(EmbeddingBuffer {
92 buffer_id: id,
93 data: vec![0.0_f32; dim],
94 dim,
95 in_use: false,
96 generation: 0,
97 });
98 }
99 let total = buffers.len();
100 Self {
101 buffers,
102 next_id: initial as u64,
103 config,
104 stats: PoolStats {
105 total_buffers: total,
106 available: total,
107 ..Default::default()
108 },
109 }
110 }
111
112 pub fn acquire(&mut self) -> Option<u64> {
117 self.stats.total_allocations += 1;
118
119 if let Some(buf) = self.buffers.iter_mut().find(|b| !b.in_use) {
121 buf.in_use = true;
122 self.stats.cache_hits += 1;
123 self.stats.in_use += 1;
124 self.stats.available = self.stats.available.saturating_sub(1);
125 return Some(buf.buffer_id);
126 }
127
128 if self.buffers.len() < self.config.max_capacity {
130 let id = self.next_id;
131 self.next_id += 1;
132 let dim = self.config.embedding_dim;
133 self.buffers.push(EmbeddingBuffer {
134 buffer_id: id,
135 data: vec![0.0_f32; dim],
136 dim,
137 in_use: true,
138 generation: 0,
139 });
140 self.stats.cache_misses += 1;
141 self.stats.total_buffers += 1;
142 self.stats.in_use += 1;
143 return Some(id);
145 }
146
147 None
149 }
150
151 pub fn write(&mut self, buffer_id: u64, data: &[f32]) -> bool {
158 let dim = self.config.embedding_dim;
159 if data.len() != dim {
160 return false;
161 }
162 match self.buffers.iter_mut().find(|b| b.buffer_id == buffer_id) {
163 Some(buf) if buf.in_use => {
164 buf.data.copy_from_slice(data);
165 true
166 }
167 _ => false,
168 }
169 }
170
171 pub fn read(&self, buffer_id: u64) -> Option<&[f32]> {
175 self.buffers
176 .iter()
177 .find(|b| b.buffer_id == buffer_id && b.in_use)
178 .map(|b| b.data.as_slice())
179 }
180
181 pub fn release(&mut self, buffer_id: u64) -> bool {
186 match self.buffers.iter_mut().find(|b| b.buffer_id == buffer_id) {
187 Some(buf) => {
188 let was_in_use = buf.in_use;
189 buf.in_use = false;
190 buf.generation += 1;
191 for v in buf.data.iter_mut() {
192 *v = 0.0;
193 }
194 self.stats.total_returns += 1;
195 if was_in_use {
196 self.stats.in_use = self.stats.in_use.saturating_sub(1);
197 self.stats.available += 1;
198 }
199 true
200 }
201 None => false,
202 }
203 }
204
205 pub fn clear_all(&mut self) {
207 for buf in self.buffers.iter_mut() {
208 if buf.in_use {
209 buf.in_use = false;
210 self.stats.in_use = self.stats.in_use.saturating_sub(1);
211 self.stats.available += 1;
212 }
213 buf.generation += 1;
214 for v in buf.data.iter_mut() {
215 *v = 0.0;
216 }
217 self.stats.total_returns += 1;
218 }
219 }
220
221 pub fn stats(&self) -> PoolStats {
223 self.stats.clone()
224 }
225}
226
227#[cfg(test)]
232mod tests {
233 use super::*;
234
235 fn make_pool(dim: usize, initial: usize, max: usize) -> SemanticEmbeddingPool {
236 SemanticEmbeddingPool::new(PoolConfig {
237 embedding_dim: dim,
238 initial_capacity: initial,
239 max_capacity: max,
240 })
241 }
242
243 #[test]
245 fn test_new_preallocates_buffers() {
246 let pool = make_pool(4, 8, 64);
247 assert_eq!(pool.buffers.len(), 8);
248 for buf in &pool.buffers {
249 assert!(!buf.in_use);
250 assert_eq!(buf.data.len(), 4);
251 assert_eq!(buf.generation, 0);
252 }
253 }
254
255 #[test]
257 fn test_new_stats_consistent() {
258 let pool = make_pool(4, 8, 64);
259 let s = pool.stats();
260 assert_eq!(s.total_buffers, 8);
261 assert_eq!(s.in_use, 0);
262 assert_eq!(s.available, 8);
263 assert_eq!(s.total_allocations, 0);
264 assert_eq!(s.total_returns, 0);
265 assert_eq!(s.cache_hits, 0);
266 assert_eq!(s.cache_misses, 0);
267 }
268
269 #[test]
271 fn test_acquire_cache_hit() {
272 let mut pool = make_pool(4, 4, 16);
273 let id = pool.acquire().expect("should return id");
274 let s = pool.stats();
275 assert_eq!(s.cache_hits, 1);
276 assert_eq!(s.cache_misses, 0);
277 assert_eq!(s.total_allocations, 1);
278 let buf = pool
280 .buffers
281 .iter()
282 .find(|b| b.buffer_id == id)
283 .expect("test: buffer with acquired id should exist");
284 assert!(buf.in_use);
285 }
286
287 #[test]
289 fn test_acquire_cache_miss() {
290 let mut pool = make_pool(4, 1, 16);
291 let _id1 = pool.acquire().expect("first acquire");
293 assert_eq!(pool.stats().cache_hits, 1);
294 let _id2 = pool.acquire().expect("second acquire");
296 let s = pool.stats();
297 assert_eq!(s.cache_hits, 1);
298 assert_eq!(s.cache_misses, 1);
299 assert_eq!(s.total_buffers, 2);
300 }
301
302 #[test]
304 fn test_acquire_returns_none_at_max() {
305 let mut pool = make_pool(4, 2, 2);
306 let _id1 = pool.acquire().expect("first");
307 let _id2 = pool.acquire().expect("second");
308 let result = pool.acquire();
309 assert!(result.is_none());
310 assert_eq!(pool.stats().total_allocations, 3);
311 }
312
313 #[test]
315 fn test_write_copies_data() {
316 let mut pool = make_pool(4, 2, 8);
317 let id = pool.acquire().expect("acquire");
318 let data = [1.0_f32, 2.0, 3.0, 4.0];
319 assert!(pool.write(id, &data));
320 let read_back = pool.read(id).expect("read");
321 assert_eq!(read_back, &data);
322 }
323
324 #[test]
326 fn test_write_wrong_dim() {
327 let mut pool = make_pool(4, 2, 8);
328 let id = pool.acquire().expect("acquire");
329 let bad = [1.0_f32, 2.0]; assert!(!pool.write(id, &bad));
331 }
332
333 #[test]
335 fn test_write_not_in_use() {
336 let mut pool = make_pool(4, 2, 8);
337 let free_id = pool.buffers[0].buffer_id;
339 assert!(!pool.write(free_id, &[1.0, 2.0, 3.0, 4.0]));
340 }
341
342 #[test]
344 fn test_write_nonexistent_id() {
345 let mut pool = make_pool(4, 2, 8);
346 assert!(!pool.write(9999, &[1.0, 2.0, 3.0, 4.0]));
347 }
348
349 #[test]
351 fn test_read_in_use() {
352 let mut pool = make_pool(3, 2, 8);
353 let id = pool.acquire().expect("acquire");
354 pool.write(id, &[0.5, 0.6, 0.7]);
355 let data = pool.read(id).expect("read");
356 assert_eq!(data, &[0.5_f32, 0.6, 0.7]);
357 }
358
359 #[test]
361 fn test_read_not_in_use() {
362 let pool = make_pool(4, 2, 8);
363 let free_id = pool.buffers[0].buffer_id;
364 assert!(pool.read(free_id).is_none());
365 }
366
367 #[test]
369 fn test_read_nonexistent_id() {
370 let pool = make_pool(4, 2, 8);
371 assert!(pool.read(9999).is_none());
372 }
373
374 #[test]
376 fn test_release_frees_and_zeroes() {
377 let mut pool = make_pool(4, 2, 8);
378 let id = pool.acquire().expect("acquire");
379 pool.write(id, &[1.0, 2.0, 3.0, 4.0]);
380 assert!(pool.release(id));
381 let buf = pool
382 .buffers
383 .iter()
384 .find(|b| b.buffer_id == id)
385 .expect("test: released buffer should exist");
386 assert!(!buf.in_use);
387 assert!(buf.data.iter().all(|&v| v == 0.0));
388 }
389
390 #[test]
392 fn test_release_increments_generation() {
393 let mut pool = make_pool(4, 2, 8);
394 let id = pool.acquire().expect("acquire");
395 let gen_before = pool
396 .buffers
397 .iter()
398 .find(|b| b.buffer_id == id)
399 .expect("test: buffer before release should exist")
400 .generation;
401 pool.release(id);
402 let gen_after = pool
403 .buffers
404 .iter()
405 .find(|b| b.buffer_id == id)
406 .expect("test: buffer after release should exist")
407 .generation;
408 assert_eq!(gen_after, gen_before + 1);
409 }
410
411 #[test]
413 fn test_release_nonexistent_id() {
414 let mut pool = make_pool(4, 2, 8);
415 assert!(!pool.release(9999));
416 }
417
418 #[test]
420 fn test_release_increments_total_returns() {
421 let mut pool = make_pool(4, 2, 8);
422 let id = pool.acquire().expect("acquire");
423 pool.release(id);
424 assert_eq!(pool.stats().total_returns, 1);
425 }
426
427 #[test]
429 fn test_released_buffer_reused() {
430 let mut pool = make_pool(4, 1, 1);
431 let id1 = pool.acquire().expect("first acquire");
432 pool.release(id1);
433 let id2 = pool.acquire().expect("second acquire");
435 assert_eq!(id1, id2);
436 assert_eq!(pool.stats().cache_hits, 2);
437 }
438
439 #[test]
441 fn test_clear_all_releases_all() {
442 let mut pool = make_pool(4, 4, 16);
443 let _id0 = pool.acquire().expect("acquire 0");
444 let _id1 = pool.acquire().expect("acquire 1");
445 let _id2 = pool.acquire().expect("acquire 2");
446 pool.clear_all();
447 assert!(pool.buffers.iter().all(|b| !b.in_use));
448 assert!(pool
449 .buffers
450 .iter()
451 .all(|b| b.data.iter().all(|&v| v == 0.0)));
452 }
453
454 #[test]
456 fn test_clear_all_increments_generations() {
457 let mut pool = make_pool(4, 3, 16);
458 let _id = pool.acquire().expect("acquire");
459 pool.clear_all();
460 for buf in &pool.buffers {
461 assert!(
462 buf.generation >= 1,
463 "generation should be at least 1 after clear_all"
464 );
465 }
466 }
467
468 #[test]
470 fn test_stats_invariant() {
471 let mut pool = make_pool(4, 6, 32);
472 let _a = pool.acquire();
473 let _b = pool.acquire();
474 let _c = pool.acquire();
475 let s = pool.stats();
476 assert_eq!(s.in_use + s.available, s.total_buffers);
477 }
478
479 #[test]
481 fn test_stats_hits_plus_misses_eq_allocations() {
482 let mut pool = make_pool(4, 2, 8);
483 for _ in 0..5 {
484 pool.acquire();
485 }
486 let s = pool.stats();
487 assert_eq!(s.cache_hits + s.cache_misses, s.total_allocations);
488 }
489
490 #[test]
492 fn test_is_valid_dim() {
493 let buf = EmbeddingBuffer {
494 buffer_id: 0,
495 data: vec![0.0; 128],
496 dim: 128,
497 in_use: false,
498 generation: 0,
499 };
500 assert!(buf.is_valid_dim(128));
501 assert!(!buf.is_valid_dim(64));
502 }
503
504 #[test]
506 fn test_pool_config_default_for() {
507 let cfg = PoolConfig::default_for(768);
508 assert_eq!(cfg.embedding_dim, 768);
509 assert_eq!(cfg.initial_capacity, 16);
510 assert_eq!(cfg.max_capacity, 256);
511 }
512
513 #[test]
515 fn test_zero_initial_capacity() {
516 let mut pool = make_pool(4, 0, 4);
517 assert_eq!(pool.buffers.len(), 0);
518 let id = pool.acquire().expect("should allocate");
519 assert_eq!(pool.stats().cache_misses, 1);
520 assert!(pool.write(id, &[1.0, 2.0, 3.0, 4.0]));
521 let data = pool.read(id).expect("read");
522 assert_eq!(data, &[1.0_f32, 2.0, 3.0, 4.0]);
523 }
524
525 #[test]
527 fn test_multiple_writes() {
528 let mut pool = make_pool(3, 2, 8);
529 let id = pool.acquire().expect("acquire");
530 pool.write(id, &[1.0, 1.0, 1.0]);
531 pool.write(id, &[9.0, 8.0, 7.0]);
532 let data = pool.read(id).expect("read");
533 assert_eq!(data, &[9.0_f32, 8.0, 7.0]);
534 }
535
536 #[test]
538 fn test_release_clears_data_before_reuse() {
539 let mut pool = make_pool(4, 1, 4);
540 let id = pool.acquire().expect("acquire");
541 pool.write(id, &[5.0, 6.0, 7.0, 8.0]);
542 pool.release(id);
543 let id2 = pool.acquire().expect("reacquire");
544 assert_eq!(id, id2);
545 let data = pool.read(id2).expect("read");
547 assert!(data.iter().all(|&v| v == 0.0));
548 }
549
550 #[test]
552 fn test_stats_available_tracks() {
553 let mut pool = make_pool(4, 4, 16);
554 assert_eq!(pool.stats().available, 4);
555 let id1 = pool.acquire().expect("a1");
556 assert_eq!(pool.stats().available, 3);
557 let id2 = pool.acquire().expect("a2");
558 assert_eq!(pool.stats().available, 2);
559 pool.release(id1);
560 assert_eq!(pool.stats().available, 3);
561 pool.release(id2);
562 assert_eq!(pool.stats().available, 4);
563 }
564}