use dashmap::DashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
#[derive(Debug)]
pub struct ShardedMappings {
id_to_idx: DashMap<u64, usize>,
idx_to_id: DashMap<usize, u64>,
next_idx: AtomicUsize,
tombstone_slots: AtomicUsize,
}
impl Default for ShardedMappings {
fn default() -> Self {
Self::new()
}
}
impl ShardedMappings {
#[must_use]
pub fn new() -> Self {
Self {
id_to_idx: DashMap::new(),
idx_to_id: DashMap::new(),
next_idx: AtomicUsize::new(0),
tombstone_slots: AtomicUsize::new(0),
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
id_to_idx: DashMap::with_capacity(capacity),
idx_to_id: DashMap::with_capacity(capacity),
next_idx: AtomicUsize::new(0),
tombstone_slots: AtomicUsize::new(0),
}
}
pub fn register(&self, id: u64) -> Option<usize> {
use dashmap::mapref::entry::Entry;
match self.id_to_idx.entry(id) {
Entry::Occupied(_) => None,
Entry::Vacant(entry) => Some(self.allocate_and_map(entry, id)),
}
}
pub fn register_or_replace(&self, id: u64) -> (usize, Option<usize>) {
use dashmap::mapref::entry::Entry;
match self.id_to_idx.entry(id) {
Entry::Occupied(entry) => {
let (new_idx, old_idx) = self.replace_occupied_mapping(entry, id);
(new_idx, Some(old_idx))
}
Entry::Vacant(entry) => (self.allocate_and_map(entry, id), None),
}
}
fn allocate_and_map(
&self,
entry: dashmap::mapref::entry::VacantEntry<'_, u64, usize>,
id: u64,
) -> usize {
let idx = self.next_idx.fetch_add(1, Ordering::Relaxed);
entry.insert(idx);
self.idx_to_id.insert(idx, id);
idx
}
fn replace_occupied_mapping(
&self,
mut entry: dashmap::mapref::entry::OccupiedEntry<'_, u64, usize>,
id: u64,
) -> (usize, usize) {
let old_idx = *entry.get();
let new_idx = self.next_idx.fetch_add(1, Ordering::Relaxed);
entry.insert(new_idx);
self.idx_to_id.remove(&old_idx);
self.idx_to_id.insert(new_idx, id);
(new_idx, old_idx)
}
pub fn register_or_replace_batch(&self, ids: &[u64]) -> Vec<(usize, Option<usize>)> {
if ids.is_empty() {
return Vec::new();
}
let all_vacant = ids.iter().all(|id| !self.id_to_idx.contains_key(id));
if all_vacant {
self.batch_insert_fast_path(ids)
} else {
self.batch_replace_slow_path(ids)
}
}
fn batch_insert_fast_path(&self, ids: &[u64]) -> Vec<(usize, Option<usize>)> {
use dashmap::mapref::entry::Entry;
let n = ids.len();
let start = self.next_idx.fetch_add(n, Ordering::Relaxed);
let mut results = Vec::with_capacity(n);
for (i, &id) in ids.iter().enumerate() {
let result = match self.id_to_idx.entry(id) {
Entry::Vacant(entry) => {
let slot_idx = start + i;
entry.insert(slot_idx);
self.idx_to_id.insert(slot_idx, id);
(slot_idx, None)
}
Entry::Occupied(entry) => {
self.tombstone_slots.fetch_add(1, Ordering::Relaxed);
let (new_idx, old_idx) = self.replace_occupied_mapping(entry, id);
(new_idx, Some(old_idx))
}
};
results.push(result);
}
results
}
fn batch_replace_slow_path(&self, ids: &[u64]) -> Vec<(usize, Option<usize>)> {
use dashmap::mapref::entry::Entry;
let mut results = Vec::with_capacity(ids.len());
for &id in ids {
let result = match self.id_to_idx.entry(id) {
Entry::Vacant(entry) => (self.allocate_and_map(entry, id), None),
Entry::Occupied(entry) => {
let (new_idx, old_idx) = self.replace_occupied_mapping(entry, id);
(new_idx, Some(old_idx))
}
};
results.push(result);
}
results
}
#[allow(dead_code)] pub fn register_batch(&self, ids: &[u64]) -> Vec<(u64, usize)> {
let mut results = Vec::with_capacity(ids.len());
for &id in ids {
if let Some(idx) = self.register(id) {
results.push((id, idx));
}
}
results
}
pub fn restore(&self, id: u64, idx: usize) {
self.id_to_idx.insert(id, idx);
self.idx_to_id.insert(idx, id);
}
pub fn remove_reverse(&self, idx: usize) {
self.idx_to_id.remove(&idx);
}
pub fn remove(&self, id: u64) -> Option<usize> {
if let Some((_, idx)) = self.id_to_idx.remove(&id) {
self.idx_to_id.remove(&idx);
Some(idx)
} else {
None
}
}
#[must_use]
pub fn get_idx(&self, id: u64) -> Option<usize> {
self.id_to_idx.get(&id).map(|r| *r)
}
#[must_use]
pub fn get_id(&self, idx: usize) -> Option<u64> {
self.idx_to_id.get(&idx).map(|r| *r)
}
#[must_use]
pub fn len(&self) -> usize {
self.id_to_idx.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.id_to_idx.is_empty()
}
#[must_use]
pub fn contains(&self, id: u64) -> bool {
self.id_to_idx.contains_key(&id)
}
pub fn iter(&self) -> impl Iterator<Item = (u64, usize)> + '_ {
self.id_to_idx.iter().map(|r| (*r.key(), *r.value()))
}
#[must_use]
pub fn next_idx(&self) -> usize {
self.next_idx.load(std::sync::atomic::Ordering::Relaxed)
}
#[must_use]
#[allow(dead_code)] pub fn tombstone_count(&self) -> usize {
self.tombstone_slots.load(Ordering::Relaxed)
}
pub fn clear(&self) {
self.id_to_idx.clear();
self.idx_to_id.clear();
self.next_idx.store(0, std::sync::atomic::Ordering::Relaxed);
self.tombstone_slots.store(0, Ordering::Relaxed);
}
#[must_use]
pub fn from_parts(
id_to_idx: std::collections::HashMap<u64, usize>,
idx_to_id: std::collections::HashMap<usize, u64>,
next_idx: usize,
) -> Self {
let sharded_id_to_idx = DashMap::with_capacity(id_to_idx.len());
let sharded_idx_to_id = DashMap::with_capacity(idx_to_id.len());
for (id, idx) in id_to_idx {
sharded_id_to_idx.insert(id, idx);
}
for (idx, id) in idx_to_id {
sharded_idx_to_id.insert(idx, id);
}
Self {
id_to_idx: sharded_id_to_idx,
idx_to_id: sharded_idx_to_id,
next_idx: AtomicUsize::new(next_idx),
tombstone_slots: AtomicUsize::new(0),
}
}
#[must_use]
pub fn as_parts(
&self,
) -> (
std::collections::HashMap<u64, usize>,
std::collections::HashMap<usize, u64>,
usize,
) {
let id_to_idx: std::collections::HashMap<u64, usize> = self
.id_to_idx
.iter()
.map(|r| (*r.key(), *r.value()))
.collect();
let idx_to_id: std::collections::HashMap<usize, u64> = self
.idx_to_id
.iter()
.map(|r| (*r.key(), *r.value()))
.collect();
let next_idx = self.next_idx.load(Ordering::SeqCst);
(id_to_idx, idx_to_id, next_idx)
}
}