ferroui_base/rendering/composition/transport/
batch_stream_array_pool.rs1use std::marker::PhantomData;
2use std::ops::Deref;
3use std::sync::{Mutex, MutexGuard, PoisonError};
4
5const USAGE_STATISTICS_LENGTH: usize = 10;
6
7pub trait IBatchStreamPoolItems<T>: Send + Sync {
10 fn create_item(&self) -> T;
12
13 fn clear_item(&self, _item: &mut T) {}
15
16 fn destroy_item(&self, _item: T) {}
18}
19
20pub type BatchStreamPoolStartTimer = Box<dyn Fn() + Send + Sync>;
22
23struct PoolState<T> {
24 pool: Vec<T>,
25 disposed: bool,
26 usage: i32,
27 usage_statistics: [i32; USAGE_STATISTICS_LENGTH],
28 usage_statistics_slot: usize,
29 timer_is_running: bool,
30 current_update_tick: u64,
31 last_activity_tick: u64,
32}
33
34pub struct BatchStreamPoolBase<T, I: IBatchStreamPoolItems<T>> {
57 items: I,
58 start_timer: Option<BatchStreamPoolStartTimer>,
59 reclaim_immediately: bool,
60 state: Mutex<PoolState<T>>,
61}
62
63impl<T, I: IBatchStreamPoolItems<T>> BatchStreamPoolBase<T, I> {
64 pub fn new(items: I, reclaim_immediately: bool, start_timer: Option<BatchStreamPoolStartTimer>) -> Self {
65 let pool = Self {
66 items,
67 start_timer,
68 reclaim_immediately,
69 state: Mutex::new(PoolState {
70 pool: Vec::new(),
71 disposed: false,
72 usage: 0,
73 usage_statistics: [0; USAGE_STATISTICS_LENGTH],
74 usage_statistics_slot: 0,
75 timer_is_running: false,
76 current_update_tick: 0,
77 last_activity_tick: 0,
78 }),
79 };
80 let start = pool.ensure_update_timer(&mut pool.lock());
81 pool.notify_start_timer(start);
82 pool
83 }
84
85 fn lock(&self) -> MutexGuard<'_, PoolState<T>> {
86 self.state.lock().unwrap_or_else(PoisonError::into_inner)
87 }
88
89 pub fn current_usage(&self) -> i32 {
91 self.lock().usage
92 }
93
94 pub fn current_pool(&self) -> i32 {
96 self.lock().pool.len() as i32
97 }
98
99 pub fn timer_is_running(&self) -> bool {
102 self.lock().timer_is_running
103 }
104
105 fn ensure_update_timer(&self, state: &mut PoolState<T>) -> bool {
108 if state.timer_is_running || !self.needs_timer(state) {
109 return false;
110 }
111
112 state.timer_is_running = true;
113 true
114 }
115
116 fn notify_start_timer(&self, start: bool) {
117 if start {
118 if let Some(start_timer) = &self.start_timer {
119 start_timer();
120 }
121 }
122 }
123
124 fn needs_timer(&self, state: &PoolState<T>) -> bool {
125 !self.reclaim_immediately
126 && state.current_update_tick.wrapping_sub(state.last_activity_tick)
127 < USAGE_STATISTICS_LENGTH as u64 * 2 + 1
128 }
129
130 pub fn update_timer_tick(&self) -> bool {
137 let mut state = self.lock();
138 state.current_update_tick = state.current_update_tick.wrapping_add(1);
139 let maximum_usage = state.usage_statistics.iter().copied().max().unwrap_or(0);
140 let recently_used_pooled_slots = maximum_usage - state.usage;
141 let keep_slots = recently_used_pooled_slots.max(10);
142 while (keep_slots as usize) < state.pool.len() {
143 if let Some(item) = state.pool.pop() {
144 self.items.destroy_item(item);
145 }
146 }
147
148 state.usage_statistics_slot = (state.usage_statistics_slot + 1) % USAGE_STATISTICS_LENGTH;
149 let slot = state.usage_statistics_slot;
150 state.usage_statistics[slot] = 0;
151
152 state.timer_is_running = self.needs_timer(&state);
153 state.timer_is_running
154 }
155
156 fn on_activity(&self, state: &mut PoolState<T>) -> bool {
157 state.last_activity_tick = state.current_update_tick;
158 self.ensure_update_timer(state)
159 }
160
161 pub fn get(&self) -> T {
163 let (item, start) = {
164 let mut state = self.lock();
165 state.usage += 1;
166 let slot = state.usage_statistics_slot;
167 if state.usage_statistics[slot] < state.usage {
168 state.usage_statistics[slot] = state.usage;
169 }
170
171 let start = self.on_activity(&mut state);
172
173 (state.pool.pop(), start)
174 };
175 self.notify_start_timer(start);
176
177 match item {
178 Some(item) => item,
179 None => self.items.create_item(),
180 }
181 }
182
183 pub fn return_item(&self, mut item: T) {
186 self.items.clear_item(&mut item);
187 let result = {
188 let mut state = self.lock();
189 state.usage -= 1;
190 if !state.disposed && !self.reclaim_immediately {
191 state.pool.push(item);
192 Ok(self.on_activity(&mut state))
193 } else {
194 Err(item)
195 }
196 };
197
198 match result {
199 Ok(start) => self.notify_start_timer(start),
200 Err(item) => self.items.destroy_item(item),
201 }
202 }
203
204 pub fn dispose(&self) {
207 let mut state = self.lock();
208 state.disposed = true;
209 for item in state.pool.drain(..) {
210 self.items.destroy_item(item);
211 }
212 }
213}
214
215impl<T, I: IBatchStreamPoolItems<T>> Drop for BatchStreamPoolBase<T, I> {
216 fn drop(&mut self) {
217 self.dispose();
218 }
219}
220
221pub struct BatchStreamObjectPoolItems<T> {
223 array_size: usize,
224 marker: PhantomData<fn() -> T>,
225}
226
227impl<T> IBatchStreamPoolItems<Box<[Option<T>]>> for BatchStreamObjectPoolItems<T> {
228 fn create_item(&self) -> Box<[Option<T>]> {
229 std::iter::repeat_with(|| None).take(self.array_size).collect()
230 }
231
232 fn clear_item(&self, item: &mut Box<[Option<T>]>) {
233 item.fill_with(|| None);
234 }
235}
236
237pub struct BatchStreamObjectPool<T> {
239 base: BatchStreamPoolBase<Box<[Option<T>]>, BatchStreamObjectPoolItems<T>>,
240}
241
242impl<T> BatchStreamObjectPool<T> {
243 pub const DEFAULT_ARRAY_SIZE: usize = 128;
245
246 pub fn new(reclaim_immediately: bool, array_size: usize, start_timer: Option<BatchStreamPoolStartTimer>) -> Self {
247 Self {
248 base: BatchStreamPoolBase::new(
249 BatchStreamObjectPoolItems { array_size, marker: PhantomData },
250 reclaim_immediately,
251 start_timer,
252 ),
253 }
254 }
255
256 pub fn array_size(&self) -> usize {
257 self.base.items.array_size
258 }
259}
260
261impl<T> Deref for BatchStreamObjectPool<T> {
262 type Target = BatchStreamPoolBase<Box<[Option<T>]>, BatchStreamObjectPoolItems<T>>;
263
264 fn deref(&self) -> &Self::Target {
265 &self.base
266 }
267}
268
269pub struct BatchStreamMemoryPoolItems {
271 buffer_size: usize,
272}
273
274impl IBatchStreamPoolItems<Box<[u8]>> for BatchStreamMemoryPoolItems {
275 fn create_item(&self) -> Box<[u8]> {
276 vec![0u8; self.buffer_size].into_boxed_slice()
277 }
278}
279
280pub struct BatchStreamMemoryPool {
285 base: BatchStreamPoolBase<Box<[u8]>, BatchStreamMemoryPoolItems>,
286}
287
288impl BatchStreamMemoryPool {
289 pub const DEFAULT_BUFFER_SIZE: usize = 1024;
291
292 pub fn new(reclaim_immediately: bool, buffer_size: usize, start_timer: Option<BatchStreamPoolStartTimer>) -> Self {
293 Self {
294 base: BatchStreamPoolBase::new(
295 BatchStreamMemoryPoolItems { buffer_size },
296 reclaim_immediately,
297 start_timer,
298 ),
299 }
300 }
301
302 pub fn buffer_size(&self) -> usize {
303 self.base.items.buffer_size
304 }
305}
306
307impl Deref for BatchStreamMemoryPool {
308 type Target = BatchStreamPoolBase<Box<[u8]>, BatchStreamMemoryPoolItems>;
309
310 fn deref(&self) -> &Self::Target {
311 &self.base
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318 use std::sync::atomic::{AtomicUsize, Ordering};
319 use std::sync::Arc;
320
321 fn assert_send_sync<T: Send + Sync>() {}
322
323 #[test]
324 fn pools_are_send_and_sync() {
325 assert_send_sync::<BatchStreamMemoryPool>();
326 assert_send_sync::<BatchStreamObjectPool<Box<dyn std::any::Any + Send>>>();
327 assert_send_sync::<BatchStreamObjectPool<Arc<str>>>();
328 }
329
330 #[derive(Default)]
332 struct Counting {
333 created: AtomicUsize,
334 cleared: AtomicUsize,
335 destroyed: AtomicUsize,
336 }
337
338 impl IBatchStreamPoolItems<usize> for Arc<Counting> {
339 fn create_item(&self) -> usize {
340 self.created.fetch_add(1, Ordering::SeqCst)
341 }
342 fn clear_item(&self, _item: &mut usize) {
343 self.cleared.fetch_add(1, Ordering::SeqCst);
344 }
345 fn destroy_item(&self, _item: usize) {
346 self.destroyed.fetch_add(1, Ordering::SeqCst);
347 }
348 }
349
350 fn counting_pool(reclaim_immediately: bool) -> (BatchStreamPoolBase<usize, Arc<Counting>>, Arc<Counting>) {
351 let counting = Arc::new(Counting::default());
352 (BatchStreamPoolBase::new(counting.clone(), reclaim_immediately, None), counting)
353 }
354
355 #[test]
356 fn get_creates_and_return_pools_items() {
357 let (pool, counting) = counting_pool(false);
358 let a = pool.get();
359 let b = pool.get();
360 assert_eq!((0, 1), (a, b));
361 assert_eq!(2, pool.current_usage());
362 assert_eq!(0, pool.current_pool());
363
364 pool.return_item(a);
365 assert_eq!(1, pool.current_usage());
366 assert_eq!(1, pool.current_pool());
367 assert_eq!(1, counting.cleared.load(Ordering::SeqCst));
368
369 pool.return_item(b);
371 assert_eq!(1, pool.get());
372 assert_eq!(0, pool.get());
373 assert_eq!(2, counting.created.load(Ordering::SeqCst));
374 assert_eq!(0, counting.destroyed.load(Ordering::SeqCst));
375 }
376
377 #[test]
378 fn reclaim_immediately_destroys_returned_items_and_needs_no_timer() {
379 let (pool, counting) = counting_pool(true);
380 assert!(!pool.timer_is_running());
381 let a = pool.get();
382 pool.return_item(a);
383 assert_eq!(0, pool.current_usage());
384 assert_eq!(0, pool.current_pool());
385 assert_eq!(1, counting.cleared.load(Ordering::SeqCst));
386 assert_eq!(1, counting.destroyed.load(Ordering::SeqCst));
387 assert!(!pool.timer_is_running());
388 assert!(!pool.update_timer_tick());
389 }
390
391 #[test]
392 fn dispose_destroys_pooled_items_and_later_returns() {
393 let (pool, counting) = counting_pool(false);
394 let items: Vec<usize> = (0..3).map(|_| pool.get()).collect();
395 pool.return_item(items[0]);
396 pool.return_item(items[1]);
397 pool.dispose();
398 assert_eq!(0, pool.current_pool());
399 assert_eq!(2, counting.destroyed.load(Ordering::SeqCst));
400
401 pool.return_item(items[2]);
402 assert_eq!(0, pool.current_pool());
403 assert_eq!(0, pool.current_usage());
404 assert_eq!(3, counting.destroyed.load(Ordering::SeqCst));
405 }
406
407 #[test]
408 fn dropping_the_pool_destroys_pooled_items() {
409 let (pool, counting) = counting_pool(false);
410 let a = pool.get();
411 pool.return_item(a);
412 drop(pool);
413 assert_eq!(1, counting.destroyed.load(Ordering::SeqCst));
414 }
415
416 #[test]
417 fn tick_keeps_at_least_ten_items() {
418 let (pool, counting) = counting_pool(false);
419 let items: Vec<usize> = (0..8).map(|_| pool.get()).collect();
420 for item in items {
421 pool.return_item(item);
422 }
423 for _ in 0..30 {
424 pool.update_timer_tick();
425 }
426 assert_eq!(8, pool.current_pool());
427 assert_eq!(0, counting.destroyed.load(Ordering::SeqCst));
428 }
429
430 #[test]
431 fn tick_trims_to_the_peak_usage_of_the_last_ten_ticks() {
432 let (pool, counting) = counting_pool(false);
433 let items: Vec<usize> = (0..30).map(|_| pool.get()).collect();
435 for item in items {
436 pool.return_item(item);
437 }
438 assert_eq!(30, pool.current_pool());
439
440 for _ in 0..9 {
443 assert!(pool.update_timer_tick());
444 assert_eq!(30, pool.current_pool());
445 }
446 assert!(pool.update_timer_tick());
448 assert_eq!(30, pool.current_pool());
449 assert!(pool.update_timer_tick());
451 assert_eq!(10, pool.current_pool());
452 assert_eq!(20, counting.destroyed.load(Ordering::SeqCst));
453 }
454
455 #[test]
456 fn items_in_use_count_against_the_slots_to_keep() {
457 let (pool, _counting) = counting_pool(false);
458 let mut items: Vec<usize> = (0..40).map(|_| pool.get()).collect();
460 for item in items.drain(25..) {
461 pool.return_item(item);
462 }
463 assert_eq!(15, pool.current_pool());
464 pool.update_timer_tick();
466 assert_eq!(15, pool.current_pool());
467
468 let more: Vec<usize> = (0..15).map(|_| pool.get()).collect();
470 assert_eq!(0, pool.current_pool());
471 for item in more {
472 pool.return_item(item);
473 }
474 for item in items {
476 pool.return_item(item);
477 }
478 pool.update_timer_tick();
479 assert_eq!(40, pool.current_pool());
480 }
481
482 #[test]
483 fn timer_stops_after_21_idle_ticks_and_restarts_on_activity() {
484 let starts = Arc::new(AtomicUsize::new(0));
485 let counting = Arc::new(Counting::default());
486 let pool = {
487 let starts = starts.clone();
488 BatchStreamPoolBase::new(
489 counting,
490 false,
491 Some(Box::new(move || {
492 starts.fetch_add(1, Ordering::SeqCst);
493 })),
494 )
495 };
496 assert_eq!(1, starts.load(Ordering::SeqCst));
498 assert!(pool.timer_is_running());
499
500 let item = pool.get();
502 pool.return_item(item);
503 assert_eq!(1, starts.load(Ordering::SeqCst));
504
505 for _ in 0..20 {
506 assert!(pool.update_timer_tick());
507 }
508 assert!(!pool.update_timer_tick());
509 assert!(!pool.timer_is_running());
510
511 let item = pool.get();
512 assert_eq!(2, starts.load(Ordering::SeqCst));
513 assert!(pool.timer_is_running());
514 pool.return_item(item);
515 assert_eq!(2, starts.load(Ordering::SeqCst));
516
517 for _ in 0..15 {
519 assert!(pool.update_timer_tick());
520 }
521 let item = pool.get();
522 pool.return_item(item);
523 for _ in 0..20 {
524 assert!(pool.update_timer_tick());
525 }
526 assert!(!pool.update_timer_tick());
527 }
528
529 #[test]
530 fn start_timer_callback_may_use_the_pool() {
531 let pool: Arc<Mutex<Option<Arc<BatchStreamMemoryPool>>>> = Arc::new(Mutex::new(None));
533 let observed = Arc::new(AtomicUsize::new(0));
534 let created = {
535 let pool = pool.clone();
536 let observed = observed.clone();
537 Arc::new(BatchStreamMemoryPool::new(
538 false,
539 16,
540 Some(Box::new(move || {
541 if let Some(pool) = pool.lock().unwrap().as_ref() {
542 observed.store(pool.current_usage() as usize, Ordering::SeqCst);
543 }
544 })),
545 ))
546 };
547 *pool.lock().unwrap() = Some(created.clone());
548 for _ in 0..21 {
549 created.update_timer_tick();
550 }
551 let buffer = created.get();
552 assert_eq!(1, observed.load(Ordering::SeqCst));
553 created.return_item(buffer);
554 *pool.lock().unwrap() = None;
555 }
556
557 #[test]
558 fn object_pool_hands_out_cleared_arrays() {
559 let pool = BatchStreamObjectPool::<Arc<str>>::new(false, BatchStreamObjectPool::<Arc<str>>::DEFAULT_ARRAY_SIZE, None);
560 assert_eq!(128, pool.array_size());
561 let mut array = pool.get();
562 assert_eq!(128, array.len());
563 assert!(array.iter().all(|slot| slot.is_none()));
564
565 let value: Arc<str> = Arc::from("value");
566 array[3] = Some(value.clone());
567 assert_eq!(2, Arc::strong_count(&value));
568 pool.return_item(array);
569 assert_eq!(1, Arc::strong_count(&value));
571 assert_eq!(1, pool.current_pool());
572
573 let array = pool.get();
574 assert!(array.iter().all(|slot| slot.is_none()));
575 }
576
577 #[test]
578 fn memory_pool_hands_out_buffers_of_the_requested_size() {
579 let pool = BatchStreamMemoryPool::new(false, BatchStreamMemoryPool::DEFAULT_BUFFER_SIZE, None);
580 assert_eq!(1024, pool.buffer_size());
581 let mut buffer = pool.get();
582 assert_eq!(1024, buffer.len());
583 buffer[0] = 42;
584 pool.return_item(buffer);
585 assert_eq!(42, pool.get()[0]);
587 }
588
589 #[test]
590 fn pool_can_be_shared_between_threads() {
591 let pool = Arc::new(BatchStreamMemoryPool::new(false, 64, None));
592 let threads: Vec<_> = (0..4)
593 .map(|_| {
594 let pool = pool.clone();
595 std::thread::spawn(move || {
596 for _ in 0..200 {
597 let buffer = pool.get();
598 pool.return_item(buffer);
599 }
600 })
601 })
602 .collect();
603 for thread in threads {
604 thread.join().unwrap();
605 }
606 assert_eq!(0, pool.current_usage());
607 assert!(pool.current_pool() >= 1 && pool.current_pool() <= 4);
608 }
609}