1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use std::rc::Rc;
use crate::{FxBuildHasher, FxHashMap};
/// A value a [`ContentPool`] holds, which tells whether a holder outside the
/// pool keeps it.
pub trait PooledValue {
/// Whether anything besides the pool holds the value.
fn is_held(&self) -> bool;
}
impl<T: ?Sized> PooledValue for Rc<T> {
fn is_held(&self) -> bool {
Self::strong_count(self) > 1
}
}
/// The values a pool takes before its first sweep.
const FIRST_SWEEP: usize = 16;
/// Values shared by their content, as the text a scene draws frame after
/// frame: a lookup hands out the value an earlier lookup made for the same
/// content, so a label drawn again takes no new copy.
///
/// Before an insertion would take the pool to twice the values its last
/// sweep kept, it drops the values nothing else holds. It keeps what the
/// screen shows, then: a text that changes every frame leaves once the
/// frames that drew it are gone, on any platform's memory.
pub struct ContentPool<V> {
values: FxHashMap<u64, V>,
sweep_at: usize,
}
impl<V: PooledValue> ContentPool<V> {
/// An empty pool.
pub const fn new() -> Self {
Self {
values: FxHashMap::with_hasher(FxBuildHasher::new()),
sweep_at: FIRST_SWEEP,
}
}
/// The number of values the pool holds.
pub fn len(&self) -> usize {
self.values.len()
}
/// Whether the pool holds no value.
pub fn is_empty(&self) -> bool {
self.values.is_empty()
}
/// `read` of the value under `hash` that `matches` accepts, which `make`
/// makes when the pool holds none. A value under the same hash that
/// `matches` refuses, another content's, gives way to the new one.
pub fn with<R>(
&mut self,
hash: u64,
matches: impl FnOnce(&V) -> bool,
make: impl FnOnce() -> V,
read: impl FnOnce(&V) -> R,
) -> R {
if let Some(value) = self.values.get(&hash)
&& matches(value)
{
return read(value);
}
if self.values.len() >= self.sweep_at {
self.sweep();
}
read(self.values.entry(hash).insert_entry(make()).get())
}
// Drops the values nothing holds. Out of line: a pool sweeps once its
// size doubles, and each caller of `with` would grow by the sweep.
#[inline(never)]
fn sweep(&mut self) {
self.values.retain(|_, value| value.is_held());
self.sweep_at = (self.values.len() * 2).max(FIRST_SWEEP);
self.values.shrink_to(self.sweep_at);
}
}
impl<V: PooledValue> Default for ContentPool<V> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "tests/content_pool_tests.rs"]
mod tests;