use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct NumberedValues<T> {
vals: HashMap<u32, T>,
next_unused_id: u32,
}
impl<T> Default for NumberedValues<T> {
#[inline]
fn default() -> Self {
Self {
vals: HashMap::new(),
next_unused_id: 0,
}
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, thiserror::Error)]
pub enum AddError {
#[error("invalid slot id {id}: next unused is {next}")]
StaleId { id: u32, next: u32 },
}
impl<T> NumberedValues<T> {
#[inline]
pub fn new() -> Self {
Self::default()
}
#[inline]
pub fn get_next(&self) -> u32 {
self.next_unused_id
}
#[inline]
pub fn get(&self, id: u32) -> Option<&T> {
self.vals.get(&id)
}
#[inline]
pub fn len(&self) -> usize {
self.vals.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.vals.is_empty()
}
pub fn add(&mut self, id: u32, value: T) -> Result<(), AddError> {
if id < self.next_unused_id {
return Err(AddError::StaleId {
id,
next: self.next_unused_id,
});
}
self.vals.insert(id, value);
self.next_unused_id = id.saturating_add(1);
Ok(())
}
pub fn iter(&self) -> impl Iterator<Item = (u32, &T)> + '_ {
self.vals.iter().map(|(&k, v)| (k, v))
}
}
pub mod forward_ref {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Unresolved<T, Loc> {
id: u32,
first_seen: Loc,
_kind: core::marker::PhantomData<fn() -> T>,
}
impl<T, Loc> Unresolved<T, Loc> {
#[inline]
pub fn pending(id: u32, first_seen: Loc) -> Self {
Self {
id,
first_seen,
_kind: core::marker::PhantomData,
}
}
#[inline]
pub fn id(&self) -> u32 {
self.id
}
#[inline]
pub fn first_seen(&self) -> &Loc {
&self.first_seen
}
#[inline]
pub fn resolve(self, value: T) -> Resolved<T, Loc> {
Resolved {
id: self.id,
first_seen: self.first_seen,
value,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Resolved<T, Loc> {
id: u32,
first_seen: Loc,
value: T,
}
impl<T, Loc> Resolved<T, Loc> {
#[inline]
pub fn id(&self) -> u32 {
self.id
}
#[inline]
pub fn first_seen(&self) -> &Loc {
&self.first_seen
}
#[inline]
pub fn get(&self) -> &T {
&self.value
}
#[inline]
pub fn into_value(self) -> T {
self.value
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_registry_starts_at_zero() {
let n: NumberedValues<u32> = NumberedValues::new();
assert_eq!(n.get_next(), 0);
assert_eq!(n.get(0), None);
assert!(n.is_empty());
}
#[test]
fn add_advances_next_unused() {
let mut n: NumberedValues<u32> = NumberedValues::new();
n.add(0, 100).expect("fresh id");
assert_eq!(n.get_next(), 1);
assert_eq!(n.get(0), Some(&100));
n.add(5, 500).expect("monotonic id");
assert_eq!(n.get_next(), 6);
assert_eq!(n.get(5), Some(&500));
assert_eq!(n.get(2), None);
}
#[test]
fn add_stale_id_is_typed_error() {
let mut n: NumberedValues<u32> = NumberedValues::new();
n.add(3, 30).unwrap();
let err = n.add(2, 20).unwrap_err();
assert_eq!(err, AddError::StaleId { id: 2, next: 4 });
}
#[test]
fn slot_mapping_shape_matches_upstream_test() {
let mut globals: NumberedValues<&'static str> = NumberedValues::new();
globals.add(0, "@0").unwrap();
assert_eq!(globals.get_next(), 1);
assert_eq!(globals.get(0), Some(&"@0"));
}
#[test]
fn forward_ref_state_transition() {
use forward_ref::Unresolved;
let pending: Unresolved<u32, ()> = Unresolved::pending(7, ());
assert_eq!(pending.id(), 7);
let resolved = pending.resolve(42);
assert_eq!(resolved.id(), 7);
assert_eq!(*resolved.get(), 42);
assert_eq!(resolved.into_value(), 42);
}
}