1 // Copyright 2018 The Chromium OS Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 use std::collections::hash_map::IterMut; 6 use std::collections::HashMap; 7 use std::io; 8 use std::ops::{Index, IndexMut}; 9 use std::slice::SliceIndex; 10 11 /// Trait that allows for checking if an implementor is dirty. Useful for types that are cached so 12 /// it can be checked if they need to be committed to disk. 13 pub trait Cacheable { 14 /// Used to check if the item needs to be written out or if it can be discarded. dirty(&self) -> bool15 fn dirty(&self) -> bool; 16 } 17 18 #[derive(Debug)] 19 /// Represents a vector that implements the `Cacheable` trait so it can be held in a cache. 20 pub struct VecCache<T: 'static + Copy + Default> { 21 vec: Box<[T]>, 22 dirty: bool, 23 } 24 25 impl<T: 'static + Copy + Default> VecCache<T> { 26 /// Creates a `VecCache` that can hold `count` elements. new(count: usize) -> VecCache<T>27 pub fn new(count: usize) -> VecCache<T> { 28 VecCache { 29 vec: vec![Default::default(); count].into_boxed_slice(), 30 dirty: true, 31 } 32 } 33 34 /// Creates a `VecCache` from the passed in `vec`. from_vec(vec: Vec<T>) -> VecCache<T>35 pub fn from_vec(vec: Vec<T>) -> VecCache<T> { 36 VecCache { 37 vec: vec.into_boxed_slice(), 38 dirty: false, 39 } 40 } 41 get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output> where I: SliceIndex<[T]>,42 pub fn get<I>(&self, index: I) -> Option<&<I as SliceIndex<[T]>>::Output> 43 where 44 I: SliceIndex<[T]>, 45 { 46 self.vec.get(index) 47 } 48 49 /// Gets a reference to the underlying vector. get_values(&self) -> &[T]50 pub fn get_values(&self) -> &[T] { 51 &self.vec 52 } 53 54 /// Mark this cache element as clean. mark_clean(&mut self)55 pub fn mark_clean(&mut self) { 56 self.dirty = false; 57 } 58 59 /// Returns the number of elements in the vector. len(&self) -> usize60 pub fn len(&self) -> usize { 61 self.vec.len() 62 } 63 } 64 65 impl<T: 'static + Copy + Default> Cacheable for VecCache<T> { dirty(&self) -> bool66 fn dirty(&self) -> bool { 67 self.dirty 68 } 69 } 70 71 impl<T: 'static + Copy + Default> Index<usize> for VecCache<T> { 72 type Output = T; 73 index(&self, index: usize) -> &T74 fn index(&self, index: usize) -> &T { 75 self.vec.index(index) 76 } 77 } 78 79 impl<T: 'static + Copy + Default> IndexMut<usize> for VecCache<T> { index_mut(&mut self, index: usize) -> &mut T80 fn index_mut(&mut self, index: usize) -> &mut T { 81 self.dirty = true; 82 self.vec.index_mut(index) 83 } 84 } 85 86 #[derive(Debug)] 87 pub struct CacheMap<T: Cacheable> { 88 capacity: usize, 89 map: HashMap<usize, T>, 90 } 91 92 impl<T: Cacheable> CacheMap<T> { new(capacity: usize) -> Self93 pub fn new(capacity: usize) -> Self { 94 CacheMap { 95 capacity, 96 map: HashMap::with_capacity(capacity), 97 } 98 } 99 contains_key(&self, key: &usize) -> bool100 pub fn contains_key(&self, key: &usize) -> bool { 101 self.map.contains_key(key) 102 } 103 get(&self, index: &usize) -> Option<&T>104 pub fn get(&self, index: &usize) -> Option<&T> { 105 self.map.get(index) 106 } 107 get_mut(&mut self, index: &usize) -> Option<&mut T>108 pub fn get_mut(&mut self, index: &usize) -> Option<&mut T> { 109 self.map.get_mut(index) 110 } 111 iter_mut(&mut self) -> IterMut<usize, T>112 pub fn iter_mut(&mut self) -> IterMut<usize, T> { 113 self.map.iter_mut() 114 } 115 116 // Check if the refblock cache is full and we need to evict. insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()> where F: FnOnce(usize, T) -> io::Result<()>,117 pub fn insert<F>(&mut self, index: usize, block: T, write_callback: F) -> io::Result<()> 118 where 119 F: FnOnce(usize, T) -> io::Result<()>, 120 { 121 if self.map.len() == self.capacity { 122 // TODO(dgreid) - smarter eviction strategy. 123 let to_evict = *self.map.iter().nth(0).unwrap().0; 124 if let Some(evicted) = self.map.remove(&to_evict) { 125 if evicted.dirty() { 126 write_callback(to_evict, evicted)?; 127 } 128 } 129 } 130 self.map.insert(index, block); 131 Ok(()) 132 } 133 } 134 135 #[cfg(test)] 136 mod tests { 137 use super::*; 138 139 struct NumCache(pub u64); 140 impl Cacheable for NumCache { dirty(&self) -> bool141 fn dirty(&self) -> bool { 142 true 143 } 144 } 145 146 #[test] evicts_when_full()147 fn evicts_when_full() { 148 let mut cache = CacheMap::<NumCache>::new(3); 149 let mut evicted = None; 150 cache 151 .insert(0, NumCache(5), |index, _| { 152 evicted = Some(index); 153 Ok(()) 154 }) 155 .unwrap(); 156 assert_eq!(evicted, None); 157 cache 158 .insert(1, NumCache(6), |index, _| { 159 evicted = Some(index); 160 Ok(()) 161 }) 162 .unwrap(); 163 assert_eq!(evicted, None); 164 cache 165 .insert(2, NumCache(7), |index, _| { 166 evicted = Some(index); 167 Ok(()) 168 }) 169 .unwrap(); 170 assert_eq!(evicted, None); 171 cache 172 .insert(3, NumCache(8), |index, _| { 173 evicted = Some(index); 174 Ok(()) 175 }) 176 .unwrap(); 177 assert!(evicted.is_some()); 178 179 // Check that three of the four items inserted are still there and that the most recently 180 // inserted is one of them. 181 let num_items = (0..=3).filter(|k| cache.contains_key(&k)).count(); 182 assert_eq!(num_items, 3); 183 assert!(cache.contains_key(&3)); 184 } 185 } 186