#![doc = include_str!("../README.md")]
#![no_std]
use core::any::TypeId;
use core::marker::PhantomData;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
pub use typeslot_macros::TypeSlot;
#[doc(hidden)]
pub use inventory;
pub mod prelude {
pub use crate::{SlotGroup, TypeSlot, init_slot, slot, try_slot};
}
pub struct AtomicSlot(AtomicUsize);
impl AtomicSlot {
pub const fn new() -> Self {
Self(AtomicUsize::new(usize::MAX))
}
pub fn get(&self) -> Option<usize> {
let val = self.0.load(Ordering::Acquire);
(val != usize::MAX).then_some(val)
}
pub fn set(&self, index: usize) {
self.0
.compare_exchange(
usize::MAX,
index,
Ordering::Release,
Ordering::Relaxed,
)
.expect("`AtomicSlot::set` called twice");
}
}
impl Default for AtomicSlot {
fn default() -> Self {
Self::new()
}
}
pub struct TypeSlotEntry {
pub type_id: TypeId,
pub group_id: TypeId,
pub slot: &'static AtomicSlot,
}
inventory::collect!(TypeSlotEntry);
pub trait TypeSlot<G: 'static>: 'static {
fn slot() -> Option<usize>
where
Self: Sized;
}
pub struct SlotGroup<G>(PhantomData<G>);
impl<G: 'static> SlotGroup<G> {
pub const fn new() -> Self {
Self(PhantomData)
}
pub fn try_get<T: TypeSlot<G>>(&self) -> Option<usize> {
try_slot::<T, G>()
}
pub fn get<T: TypeSlot<G>>(&self) -> usize {
slot::<T, G>()
}
}
impl<G: 'static> Default for SlotGroup<G> {
fn default() -> Self {
Self::new()
}
}
pub fn slot<T: TypeSlot<G>, G: 'static>() -> usize {
T::slot().expect("slot not initialized; call `init_slot` first")
}
pub fn try_slot<T: TypeSlot<G>, G: 'static>() -> Option<usize> {
T::slot()
}
pub fn init_slot<G: 'static>() -> usize {
let group_id = TypeId::of::<G>();
let mut index = 0usize;
for entry in inventory::iter::<TypeSlotEntry>() {
if entry.group_id == group_id {
entry.slot.set(index);
index += 1;
}
}
index
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn atomic_slot_starts_unset() {
let slot = AtomicSlot::new();
assert_eq!(slot.get(), None);
}
#[test]
fn atomic_slot_set_and_get() {
let slot = AtomicSlot::new();
slot.set(42);
assert_eq!(slot.get(), Some(42));
}
#[test]
#[should_panic(expected = "`AtomicSlot::set` called twice")]
fn atomic_slot_panics_on_double_set() {
let slot = AtomicSlot::new();
slot.set(1);
slot.set(2);
}
}