forked from feather-rs/feather
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.rs
More file actions
152 lines (145 loc) · 5.4 KB
/
cache.rs
File metadata and controls
152 lines (145 loc) · 5.4 KB
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use std::{
collections::VecDeque,
sync::Arc,
time::{Duration, Instant},
};
use ahash::AHashMap;
use base::{ChunkHandle, ChunkPosition};
#[cfg(not(test))]
const CACHE_TIME: Duration = Duration::from_secs(30);
#[cfg(test)]
const CACHE_TIME: Duration = Duration::from_millis(500);
/// This struct contains chunks that were unloaded but remain in memory in case they are needed.
#[derive(Default)]
pub struct ChunkCache {
map: AHashMap<ChunkPosition, (Instant, ChunkHandle)>, // expire time + handle
unload_queue: VecDeque<ChunkPosition>,
}
impl ChunkCache {
pub fn new() -> Self {
Self {
map: AHashMap::new(),
unload_queue: VecDeque::new(),
}
}
/// Purges all unused chunk handles. Handles that exist elswhere in the memory are not removed.
pub fn purge_unused(&mut self) {
let mut to_remove: Vec<ChunkPosition> = vec![];
for (pos, (_, arc)) in self.map.iter() {
if Arc::strong_count(arc) == 1 {
to_remove.push(*pos)
}
}
for i in to_remove {
self.map.remove(&i);
}
}
/// Purges all chunk handles in the cache, including those that exist elswhere.
pub fn purge_all(&mut self) {
self.map.clear();
self.unload_queue.clear();
}
fn ref_count(&self, pos: &ChunkPosition) -> Option<usize> {
self.map.get(pos).map(|(_, arc)| Arc::strong_count(arc))
}
/// Purges all chunks that have been in unused the cache for longer than `CACHE_TIME`. Refreshes this timer for chunks that are in use at the moment.
pub fn purge_old_unused(&mut self) {
while let Some(&pos) = self.unload_queue.get(0) {
if !self.contains(&pos) {
// Might be caused by a manual purge
self.unload_queue.pop_front();
continue;
}
if self.map.get(&pos).unwrap().0 > Instant::now() {
// Subsequent entries are 'scheduled' for later
break;
}
self.unload_queue.pop_front();
if self.ref_count(&pos).unwrap() > 1 {
// Another copy of this handle already exists
self.unload_queue.push_back(pos);
self.map.entry(pos).and_modify(|(time, _)| {
*time = Instant::now() + CACHE_TIME;
});
} else {
self.map.remove_entry(&pos);
}
}
}
/// Returns whether the chunk at the position is cached.
pub fn contains(&self, pos: &ChunkPosition) -> bool {
self.map.contains_key(pos)
}
/// Inserts a chunk handle into the cache, returning the previous handle if there was one.
pub fn insert(&mut self, pos: ChunkPosition, handle: ChunkHandle) -> Option<ChunkHandle> {
self.unload_queue.push_back(pos);
self.map
.insert(pos, (Instant::now() + CACHE_TIME, handle))
.map(|(_, handle)| handle)
}
/// Inserts a chunk handle into the cache. Reads the chunk's position by locking it. Blocks.
pub fn insert_read_pos(&mut self, handle: ChunkHandle) -> Option<ChunkHandle> {
let pos = handle.read().position();
self.insert(pos, handle)
}
/// Removes the chunk handle at the given position, returning the handle if it was cached.
pub fn remove(&mut self, pos: ChunkPosition) -> Option<ChunkHandle> {
self.map.remove(&pos).map(|(_, handle)| handle)
}
/// Returns the chunk handle at the given position, if there was one.
pub fn get(&mut self, pos: ChunkPosition) -> Option<ChunkHandle> {
self.map.get(&pos).map(|(_, handle)| handle.clone())
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}
#[cfg(test)]
mod tests {
use std::{sync::Arc, thread::sleep};
use base::{Chunk, ChunkHandle, ChunkLock, ChunkPosition};
use super::{ChunkCache, CACHE_TIME};
#[test]
fn purge_unused() {
let mut cache = ChunkCache::new();
let mut stored_handles: Vec<ChunkHandle> = vec![];
let mut used_count = 0;
for i in 0..100 {
let handle = Arc::new(ChunkLock::new(Chunk::new(ChunkPosition::new(i, 0)), false));
if rand::random::<bool>() {
// clone this handle and pretend it is used
used_count += 1;
stored_handles.push(handle.clone());
}
assert!(cache.insert_read_pos(handle).is_none());
}
assert_eq!(cache.len(), 100);
cache.purge_unused();
assert_eq!(cache.len(), used_count);
}
#[test]
fn purge_old_unused() {
let mut cache = ChunkCache::new();
let mut stored_handles: Vec<ChunkHandle> = vec![];
let mut used_count = 0;
for i in 0..100 {
let handle = Arc::new(ChunkLock::new(Chunk::new(ChunkPosition::new(i, 0)), false));
if rand::random::<bool>() {
// clone this handle and pretend it is used
used_count += 1;
stored_handles.push(handle.clone());
}
assert!(cache.insert_read_pos(handle).is_none());
}
cache.purge_old_unused();
assert_eq!(cache.len(), 100);
sleep(CACHE_TIME);
sleep(CACHE_TIME);
assert_eq!(cache.len(), 100);
cache.purge_old_unused();
assert_eq!(cache.len(), used_count);
}
}