#[cfg(feature = "alloc")]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "std")]
use std::cell::Cell;
#[cfg(feature = "std")]
std::thread_local! {
static CORE_TICKET: Cell<Option<usize>> = const { Cell::new(None) };
}
#[cfg(feature = "std")]
static NEXT_CORE: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "std")]
fn local_index(count: usize) -> usize {
let ticket = CORE_TICKET.with(|cell| {
cell.get().unwrap_or_else(|| {
let assigned = NEXT_CORE.fetch_add(1, Ordering::Relaxed);
cell.set(Some(assigned));
assigned
})
});
ticket % count
}
#[cfg(feature = "alloc")]
pub struct PerCore<T> {
slots: Vec<T>,
count: usize,
}
#[cfg(feature = "alloc")]
impl<T> PerCore<T> {
pub fn new_with(count: usize, factory: impl FnMut(usize) -> T) -> Self {
let slots = (0..count).map(factory).collect();
Self { slots, count }
}
pub fn from_vec(slots: Vec<T>) -> Self {
let count = slots.len();
Self { slots, count }
}
#[cfg(feature = "std")]
pub fn local(&self) -> &T {
&self.slots[local_index(self.count)]
}
pub fn slot(&self, index: usize) -> &T {
&self.slots[index]
}
#[must_use]
pub fn count(&self) -> usize {
self.count
}
}
pub struct StaticPerCore<T, const N: usize> {
slots: [T; N],
}
impl<T, const N: usize> StaticPerCore<T, N> {
pub fn new_with(factory: impl FnMut(usize) -> T) -> Self {
Self {
slots: core::array::from_fn(factory),
}
}
#[cfg(feature = "std")]
pub fn local(&self) -> &T {
&self.slots[local_index(N)]
}
pub fn slot(&self, index: usize) -> &T {
&self.slots[index]
}
#[must_use]
pub const fn count(&self) -> usize {
N
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn static_per_core_routes_by_index() {
let cores = StaticPerCore::<usize, 4>::new_with(|index| index * 10);
assert_eq!(cores.count(), 4);
assert_eq!(*cores.slot(0), 0);
assert_eq!(*cores.slot(3), 30);
}
#[cfg(feature = "alloc")]
#[test]
fn per_core_new_with_and_from_vec() {
let built = PerCore::new_with(3, |index| index + 1);
assert_eq!(built.count(), 3);
assert_eq!(*built.slot(2), 3);
let from = PerCore::from_vec(alloc::vec![10usize, 20]);
assert_eq!(from.count(), 2);
assert_eq!(*from.slot(1), 20);
}
#[cfg(feature = "std")]
#[test]
fn local_is_sticky_within_a_thread() {
let cores = StaticPerCore::<usize, 8>::new_with(|index| index);
let first = *cores.local();
assert_eq!(*cores.local(), first);
assert!(first < cores.count());
}
}