1use indexmap::IndexMap;
2use parking_lot::RwLock;
3use rand::Rng;
4use std::{
5 collections::hash_map::RandomState,
6 hash::BuildHasher,
7 sync::{
8 atomic::{AtomicU64, Ordering},
9 Arc,
10 },
11};
12
13#[derive(Clone)]
14pub struct Cache<TKey: Clone + std::hash::Hash + Eq + Send + Sync, TData: Clone + Send + Sync, S = RandomState> {
15 map: Arc<RwLock<IndexMap<TKey, TData, S>>>,
17 size: usize,
18 counters: Arc<TxScriptCacheCounters>,
19}
20
21impl<TKey: Clone + std::hash::Hash + Eq + Send + Sync, TData: Clone + Send + Sync, S: BuildHasher + Default> Cache<TKey, TData, S> {
22 pub fn new(size: u64) -> Self {
23 Self::with_counters(size, Default::default())
24 }
25
26 pub fn with_counters(size: u64, counters: Arc<TxScriptCacheCounters>) -> Self {
27 Self {
28 map: Arc::new(RwLock::new(IndexMap::with_capacity_and_hasher(size as usize, S::default()))),
29 size: size as usize,
30 counters,
31 }
32 }
33
34 pub(crate) fn get(&self, key: &TKey) -> Option<TData> {
35 self.map.read().get(key).cloned().inspect(|_data| {
36 self.counters.get_counts.fetch_add(1, Ordering::Relaxed);
37 })
38 }
39
40 pub(crate) fn insert(&self, key: TKey, data: TData) {
41 if self.size == 0 {
42 return;
43 }
44 let mut write_guard = self.map.write();
45 if write_guard.len() == self.size {
46 write_guard.swap_remove_index(rand::thread_rng().gen_range(0..self.size));
47 }
48 write_guard.insert(key, data);
49 self.counters.insert_counts.fetch_add(1, Ordering::Relaxed);
50 }
51}
52
53#[derive(Default)]
54pub struct TxScriptCacheCounters {
55 pub insert_counts: AtomicU64,
56 pub get_counts: AtomicU64,
57}
58
59impl TxScriptCacheCounters {
60 pub fn snapshot(&self) -> TxScriptCacheCountersSnapshot {
61 TxScriptCacheCountersSnapshot {
62 insert_counts: self.insert_counts.load(Ordering::Relaxed),
63 get_counts: self.get_counts.load(Ordering::Relaxed),
64 }
65 }
66}
67
68#[derive(Debug, PartialEq, Eq)]
69pub struct TxScriptCacheCountersSnapshot {
70 pub insert_counts: u64,
71 pub get_counts: u64,
72}
73
74impl TxScriptCacheCountersSnapshot {
75 pub fn hit_ratio(&self) -> f64 {
76 if self.insert_counts > 0 {
77 self.get_counts as f64 / self.insert_counts as f64
78 } else {
79 0f64
80 }
81 }
82}
83
84impl core::ops::Sub for &TxScriptCacheCountersSnapshot {
85 type Output = TxScriptCacheCountersSnapshot;
86
87 fn sub(self, rhs: Self) -> Self::Output {
88 Self::Output {
89 insert_counts: self.insert_counts.saturating_sub(rhs.insert_counts),
90 get_counts: self.get_counts.saturating_sub(rhs.get_counts),
91 }
92 }
93}