extern crate alloc;
use super::displace::Placed;
use super::table::{Entry, HomeGuard, Table, Word};
use super::{Blocked, HopscotchMap};
use crate::sync::spin_hint;
use core::borrow::Borrow;
use core::hash::{BuildHasher, Hash};
use core::sync::atomic::Ordering;
use kovan::{NodeBox, pin, retire_node};
#[cfg(test)]
use super::pause;
impl<K, V, S> HopscotchMap<K, V, S>
where
K: Hash + Eq + Clone + Send + 'static,
V: Clone + Send + 'static,
S: BuildHasher,
{
pub fn remove_if<Q, F>(&self, key: &Q, pred: F) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
F: FnOnce(&V) -> bool,
{
self.remove_where(key, pred)
}
pub fn compare_and_remove<Q>(&self, key: &Q, expected: &V) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
V: PartialEq,
{
self.remove_where(key, |value| value == expected)
}
pub fn replace_if<F>(&self, key: K, value: V, pred: F) -> Result<V, Option<V>>
where
F: FnOnce(&V) -> bool,
{
let hash = self.hasher.hash_one(&key);
loop {
self.wait_for_resize();
let guard = pin();
let (table, home) = match self.home_of(hash, &guard, true) {
Ok(held) => held,
Err(Blocked::Vacant) => return Err(None),
Err(Blocked::Held(_)) => {
#[cfg(test)]
pause::at(pause::Point::WriterMetHeldGuard);
spin_hint();
continue;
}
Err(Blocked::Resizing) => continue,
};
let Some((offset, word)) = table.find_held(&home, hash, &key, &guard) else {
return Err(None);
};
let Some(found) = word.entry() else {
return Err(None);
};
if !pred(&found.value) {
return Err(Some(found.value.clone()));
}
let old = found.value.clone();
unsafe {
table.replace_held(&home, offset, word.ptr(), Entry::boxed(hash, key, value))
};
return Ok(old);
}
}
pub fn compare_and_swap(&self, key: K, expected: &V, value: V) -> Result<V, Option<V>>
where
V: PartialEq,
{
self.replace_if(key, value, |present| present == expected)
}
pub fn compute<F>(&self, key: K, f: F) -> Option<V>
where
F: FnOnce(Option<&V>) -> Option<V>,
{
let hash = self.hasher.hash_one(&key);
loop {
self.wait_for_resize();
let guard = pin();
let (table, mut home) = match self.home_of(hash, &guard, false) {
Ok(held) => held,
Err(Blocked::Held(_)) => {
#[cfg(test)]
pause::at(pause::Point::WriterMetHeldGuard);
spin_hint();
continue;
}
Err(_) => continue,
};
if let Some((offset, word)) = table.find_held(&home, hash, &key, &guard)
&& let Some(found) = word.entry()
{
let found_ptr = word.ptr();
return match f(Some(&found.value)) {
Some(new) => {
let answer = new.clone();
unsafe {
table.replace_held(
&home,
offset,
found_ptr,
Entry::boxed(hash, key, new),
);
}
Some(answer)
}
None => {
let shrink_to = self.unlink_held(table, &mut home, offset);
drop(home);
unsafe { retire_node(found_ptr) };
if let Some(capacity) = shrink_to {
drop(guard);
self.try_resize(capacity);
}
None
}
};
}
let offset = match Self::place(table, &mut home, Word::reserved(), &guard) {
Placed::At(offset) => offset,
Placed::Contended => {
drop(home);
spin_hint();
continue;
}
Placed::Full => {
let capacity = table.capacity * 2;
drop(home);
drop(guard);
self.try_resize(capacity);
continue;
}
};
let reserved = Reservation {
table,
home: &mut home,
offset,
};
#[cfg(test)]
pause::at(pause::Point::ComputeReserved);
let new = f(None)?;
let answer = new.clone();
reserved.fill(Entry::boxed(hash, key, new));
let grow_to = self.count_in(table);
drop(home);
if let Some(capacity) = grow_to {
drop(guard);
self.try_resize(capacity);
}
return Some(answer);
}
}
#[inline(always)]
pub(super) fn remove_where<Q>(&self, key: &Q, pred: impl FnOnce(&V) -> bool) -> Option<V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
let hash = self.hasher.hash_one(key);
loop {
self.wait_for_resize();
let guard = pin();
let (table, mut home) = match self.home_of(hash, &guard, true) {
Ok(held) => held,
Err(Blocked::Vacant) => return None,
Err(Blocked::Held(_)) => {
#[cfg(test)]
pause::at(pause::Point::WriterMetHeldGuard);
spin_hint();
continue;
}
Err(Blocked::Resizing) => continue,
};
let (offset, word) = table.find_held(&home, hash, key, &guard)?;
let found = word.entry()?;
if !pred(&found.value) {
return None;
}
let old_value = found.value.clone();
let entry_ptr = word.ptr();
let shrink_to = self.unlink_held(table, &mut home, offset);
drop(home);
unsafe { retire_node(entry_ptr) };
if let Some(capacity) = shrink_to {
drop(guard);
self.try_resize(capacity);
}
return Some(old_value);
}
}
}
struct Reservation<'r, 'g, K, V> {
table: &'g Table<K, V>,
home: &'r mut HomeGuard<'g>,
offset: usize,
}
impl<K, V> Reservation<'_, '_, K, V> {
#[inline]
fn fill(self, entry: NodeBox<Entry<K, V>>) {
self.table
.get_bucket(self.home.idx + self.offset)
.store(Word::of(entry), Ordering::Release);
core::mem::forget(self);
}
}
impl<K, V> Drop for Reservation<'_, '_, K, V> {
fn drop(&mut self) {
self.table
.get_bucket(self.home.idx + self.offset)
.store(Word::free(), Ordering::Release);
self.home.stage_unlinked(self.offset);
}
}