use crate::engine::bucket::Bucket;
use crate::engine::hash::compute_hash;
use crate::engine::slab::SlabPool;
use crate::SlotState;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Clone, Copy, Default)]
pub(crate) struct SlotTTL {
pub epoch: u64,
pub ttl: u64,
}
pub struct PulseMapRaw {
pub(crate) buckets: Vec<Bucket>,
pub(crate) slab_pool: SlabPool,
num_buckets: usize,
bucket_mask: usize,
count: usize,
eviction_count: usize,
slots_ttl: Vec<SlotTTL>,
current_epoch: u64,
default_ttl: u64,
}
unsafe impl Send for PulseMapRaw {}
impl PulseMapRaw {
pub fn new(num_buckets: usize) -> Self {
let actual = num_buckets.max(1).next_power_of_two();
let buckets = (0..actual).map(|_| Bucket::empty()).collect();
let slots_ttl = vec![SlotTTL::default(); actual * 4];
Self {
buckets,
slab_pool: SlabPool::new(),
num_buckets: actual,
bucket_mask: actual - 1,
count: 0,
eviction_count: 0,
slots_ttl,
current_epoch: 0,
default_ttl: 0,
}
}
#[inline]
pub fn set_ttl(&mut self, ttl_epochs: u64) {
self.default_ttl = ttl_epochs;
}
#[inline]
pub fn get_ttl(&self) -> u64 {
self.default_ttl
}
#[inline]
pub fn current_epoch(&self) -> u64 {
self.current_epoch
}
#[inline]
fn is_expired(&self, bucket_idx: usize, slot_idx: u8) -> bool {
let entry = self.slots_ttl[bucket_idx * 4 + slot_idx as usize];
let effective_ttl = if entry.ttl == 0 {
self.default_ttl
} else {
entry.ttl
};
if effective_ttl == 0 || effective_ttl == u64::MAX {
return false;
}
self.current_epoch.wrapping_sub(entry.epoch) > effective_ttl
}
#[inline]
fn stamp_slot_ttl(&mut self, bucket_idx: usize, slot_idx: u8, ttl: u64) {
self.slots_ttl[bucket_idx * 4 + slot_idx as usize] = SlotTTL {
epoch: self.current_epoch,
ttl,
};
}
pub fn insert(&mut self, key: &[u8], value: &[u8]) {
self.insert_internal(key, value, 0);
}
pub fn insert_ttl(&mut self, key: &[u8], value: &[u8], ttl: u64) {
self.insert_internal(key, value, ttl);
}
fn insert_internal(&mut self, key: &[u8], value: &[u8], ttl: u64) {
self.current_epoch = self.current_epoch.wrapping_add(1);
let hr = compute_hash(key);
let bucket_idx = (hr.h1 as usize) & self.bucket_mask;
let bucket = &mut self.buckets[bucket_idx];
let mask = bucket.meta.match_mask(hr.h2);
let mut m = mask;
while m != 0 {
let slot_idx = m.trailing_zeros() as u8;
m &= m - 1;
let slot = &bucket.slots[slot_idx as usize];
if slot.matches_key(key, &hr, &self.slab_pool) {
if slot.get_mode() == 1 {
self.slab_pool.free(slot.slab_idx());
}
let s = &mut bucket.slots[slot_idx as usize];
if key.len() <= 6 && value.len() <= 7 {
s.set_inline(key, value);
} else {
let idx = self.slab_pool.alloc(key, value);
s.set_slab(hr.ext_fp_hi, hr.ext_fp, idx);
}
bucket.meta.on_access(slot_idx);
self.stamp_slot_ttl(bucket_idx, slot_idx, ttl);
return;
}
}
let (target_slot, is_eviction) = if let Some(free) = self.find_free_or_expired(bucket_idx) {
let is_ev = self.buckets[bucket_idx].meta.get_state(free) == SlotState::Full;
if is_ev {
let old_slot = &self.buckets[bucket_idx].slots[free as usize];
if old_slot.get_mode() == 1 {
self.slab_pool.free(old_slot.slab_idx());
}
self.eviction_count += 1;
}
(free, is_ev)
} else if let Some(evict) = self.buckets[bucket_idx].meta.find_evict_target() {
let old_slot = &self.buckets[bucket_idx].slots[evict as usize];
if old_slot.get_mode() == 1 {
self.slab_pool.free(old_slot.slab_idx());
}
self.eviction_count += 1;
(evict, true)
} else {
return;
};
let slot = &mut self.buckets[bucket_idx].slots[target_slot as usize];
if key.len() <= 6 && value.len() <= 7 {
slot.set_inline(key, value);
} else {
let idx = self.slab_pool.alloc(key, value);
slot.set_slab(hr.ext_fp_hi, hr.ext_fp, idx);
}
self.buckets[bucket_idx]
.meta
.set_state(target_slot, SlotState::Full);
self.buckets[bucket_idx].meta.set_h2(target_slot, hr.h2);
self.buckets[bucket_idx].meta.on_insert(target_slot);
self.stamp_slot_ttl(bucket_idx, target_slot, ttl);
if !is_eviction {
self.count += 1;
}
}
fn find_free_or_expired(&self, bucket_idx: usize) -> Option<u8> {
let bucket = &self.buckets[bucket_idx];
for i in 0..4u8 {
let state = bucket.meta.get_state(i);
if state != SlotState::Full {
return Some(i); }
if self.is_expired(bucket_idx, i) {
return Some(i);
}
}
None
}
pub fn get(&self, key: &[u8]) -> Option<&[u8]> {
let hr = compute_hash(key);
let bucket_idx = (hr.h1 as usize) & self.bucket_mask;
#[cfg(target_arch = "x86_64")]
unsafe {
let ptr = self.buckets.as_ptr().add(bucket_idx) as *const i8;
core::arch::x86_64::_mm_prefetch(ptr, core::arch::x86_64::_MM_HINT_T0);
}
let bucket = &self.buckets[bucket_idx];
let mask = bucket.meta.match_mask(hr.h2);
let mut m = mask;
while m != 0 {
let slot_idx = m.trailing_zeros() as u8;
m &= m - 1;
let slot = &bucket.slots[slot_idx as usize];
if slot.matches_key(key, &hr, &self.slab_pool) {
if self.is_expired(bucket_idx, slot_idx) {
return None;
}
bucket.meta.on_access(slot_idx);
return Some(slot.get_value(&self.slab_pool));
}
}
None
}
pub fn peek(&self, key: &[u8]) -> Option<&[u8]> {
let hr = compute_hash(key);
let bucket_idx = (hr.h1 as usize) & self.bucket_mask;
let bucket = &self.buckets[bucket_idx];
let mask = bucket.meta.match_mask(hr.h2);
let mut m = mask;
while m != 0 {
let slot_idx = m.trailing_zeros() as u8;
m &= m - 1;
let slot = &bucket.slots[slot_idx as usize];
if slot.matches_key(key, &hr, &self.slab_pool) {
if self.is_expired(bucket_idx, slot_idx) {
return None;
}
return Some(slot.get_value(&self.slab_pool));
}
}
None
}
pub fn remove(&mut self, key: &[u8]) -> bool {
let hr = compute_hash(key);
let bucket_idx = (hr.h1 as usize) & self.bucket_mask;
let bucket = &mut self.buckets[bucket_idx];
let mask = bucket.meta.match_mask(hr.h2);
let mut m = mask;
while m != 0 {
let slot_idx = m.trailing_zeros() as u8;
m &= m - 1;
let slot = &bucket.slots[slot_idx as usize];
if slot.matches_key(key, &hr, &self.slab_pool) {
if slot.get_mode() == 1 {
self.slab_pool.free(slot.slab_idx());
}
bucket.meta.set_state(slot_idx, SlotState::Tombstone);
bucket.slots[slot_idx as usize].clear();
self.count -= 1;
return true;
}
}
false
}
#[inline]
pub fn len(&self) -> usize {
self.count
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.num_buckets * 4
}
#[inline]
pub fn num_buckets(&self) -> usize {
self.num_buckets
}
#[inline]
pub fn load_factor(&self) -> f64 {
self.count as f64 / self.capacity() as f64
}
#[inline]
pub fn eviction_count(&self) -> usize {
self.eviction_count
}
}