#![forbid(missing_docs)]
#![forbid(clippy::missing_docs_in_private_items)]
use std::{
borrow::Borrow,
fmt,
hash::{Hash, Hasher},
sync::{Arc, Weak}
};
use parking_lot::Mutex;
use hashbrown::HashSet;
use paste::paste;
macro_rules! impl_Bag {
($int_type:ident $int_name:ident) => {
paste! {
#[doc = "Internal representation of the [`IdBag" $int_name "`]."]
#[derive(Default)]
pub struct [<InnerBag $int_name>] {
set: HashSet<$int_type>,
idgen: $int_type
}
}
paste! {
#[doc = "A collection of allocatable `" $int_type "` integers."]
#[derive(Default)]
#[repr(transparent)]
pub struct [<IdBag $int_name>](Arc<Mutex<[<InnerBag $int_name>]>>);
}
paste! {
impl [<IdBag $int_name>] {
#[doc = "Create a new id bag containing `" $int_type "` integers."]
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[doc = r#"
Create a new id generator using a pre-existing set.
This is useful when multiple `IdBag` instances should share the
same inner context. The practical implication of this is that the
`IdBag`'s that share the same inner context will not yield overlapping
identifier values.
```
use idbag::IdBag"# $int_name r#";
let idbag1 = IdBag"# $int_name r#"::new();
let idbag2 = IdBag"# $int_name r#"::with_inner(idbag1.clone_inner());
let id1 = idbag1.alloc();
assert_eq!(id1.val(), 1);
let id2 = idbag2.alloc();
assert_eq!(id2.val(), 2);
```
"#]
pub const fn with_inner(
inner: Arc<Mutex<[<InnerBag $int_name>]>>
) -> Self {
Self(inner)
}
#[must_use]
pub fn clone_inner(&self) -> Arc<Mutex<[<InnerBag $int_name>]>> {
Arc::clone(&self.0)
}
#[doc = r#"
Allocate a new identifier.
```
use idbag::IdBag"# $int_name r#";
let idbag = IdBag"# $int_name r#"::new();
let id = idbag.alloc();
assert_eq!(id.val(), 1);
let id = idbag.alloc();
assert_eq!(id.val(), 2);
```
# Desgin flaws
This function will hang if all the id's have been exhausted.
"#]
#[must_use]
pub fn alloc(&self) -> [<Id $int_name>] {
let mut g = self.0.lock();
let id = loop {
g.idgen = g.idgen.wrapping_add(1);
if !g.set.contains(&g.idgen) {
break g.idgen;
}
};
g.set.insert(id);
drop(g);
[<Id $int_name>] {
id,
idbag: Arc::downgrade(&self.0)
}
}
#[doc = r#"
Allocate a specific id, if available.
```
use idbag::IdBag"# $int_name r#";
let idbag = IdBag"# $int_name r#"::new();
let Some(id) = idbag.alloc_id(42) else {
panic!("Unexpectedly unable to allocate id 42");
};
assert_eq!(id.val(), 42);
let id = idbag.alloc_id(42);
assert_eq!(id, None);
```
"#]
#[must_use]
pub fn alloc_id(&self, id: $int_type) -> Option<[<Id $int_name>]> {
let mut g = self.0.lock();
if g.set.contains(&id) {
None
} else {
g.set.insert(id);
drop(g);
Some([<Id $int_name>] {
id,
idbag: Arc::downgrade(&self.0)
})
}
}
}
}
paste! {
#[doc = r#"
An allocated `"# $int_type r#"` identifier.
"#]
pub struct [<Id $int_name>] {
id: $int_type,
idbag: Weak<Mutex<[<InnerBag $int_name>]>>
}
impl fmt::Debug for [<Id $int_name>] {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Id:{}", self.id)
}
}
impl Hash for [<Id $int_name>] {
fn hash<H>(&self, state: &mut H)
where
H: Hasher
{
self.id.hash(state);
}
}
impl PartialEq for [<Id $int_name>] {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for [<Id $int_name>] {}
impl Borrow<$int_type> for [<Id $int_name>] {
#[doc = r#"
Allow `Id"# $int_name r#"` to be borrowed as a `&"# $int_type r#"`.
This allows a `HashMap<Id"# $int_name r#", T>` to look up entries
using a `&"# $int_type r#"`.
"#]
fn borrow(&self) -> &$int_type {
&self.id
}
}
impl [<Id $int_name>] {
#[doc = r#"Return the internal `"# $int_type r#"` value."#]
#[must_use]
#[deprecated(since = "0.2.0", note = "Use `get()` method instead.")]
pub const fn val(&self) -> $int_type {
self.id
}
#[doc = r#"Return the internal `"# $int_type r#"` value."#]
#[must_use]
pub const fn get(&self) -> $int_type {
self.id
}
#[doc = r#"
Turn object into an [`ArcId"# $int_name r#"`].
```
let idbag = idbag::IdBag"# $int_name r#"::new();
let aid = idbag.alloc().into_arcid();
```
"#]
#[must_use]
pub fn into_arcid(self) -> [<ArcId $int_name>] {
[<ArcId $int_name>](Arc::new(self))
}
}
impl Drop for [<Id $int_name>] {
#[doc = r#"
When an [`Id"# $int_name r#"`] is dropped, return the internal integer
to the collection.
"#]
fn drop(&mut self) {
if let Some(idbag) = self.idbag.upgrade() {
let mut g = idbag.lock();
g.set.remove(&self.id);
}
}
}
}
paste! {
#[doc = r#"
An atomically referenced counted version of [`Id"# $int_name r#"`].
This exists primarily because it should be possible to look up
`Arc<Id"# $int_name r#">`'s by `"# $int_type r#"`'s in `HashMap`'s.
While it is possible to look up `Id`'s in `HashMap`'s using
`&"# $int_type r#"`'s, it doesn't work for `Arc<Id"# $int_name r#">`'s.
To workaround this limitation this `ArcId"# $int_name r#"` newtype
implements `Borrow<"# $int_type r#">`, which passes it through the
`Arc` to it's inner `Id"# $int_name r#"`.
"#]
#[derive(Clone, Hash, PartialEq, Eq)]
#[repr(transparent)]
pub struct [<ArcId $int_name>](Arc<[<Id $int_name>]>);
impl fmt::Debug for [<ArcId $int_name>] {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Id:{}", self.0.id)
}
}
impl [<ArcId $int_name>] {
#[doc = r#"
Return the internal identifier `"# $int_type r#"` value.
"#]
#[must_use]
pub fn val(&self) -> $int_type {
self.0.get()
}
#[doc = r#"
Returns the inner value, if the `ArcId` has exactly one strong
reference.
# Errors
If there's more than one strong reference to the
`ArcId"# $int_name r#"`, the `ArcId"# $int_name r#"` will be returned.
"#]
pub fn try_unwrap(this: Self) -> Result<[<Id $int_name>], Self> {
Arc::<[<Id $int_name>]>::try_unwrap(this.0)
.map_err([<ArcId $int_name>])
}
#[doc = r#"
Returns the inner value, if the `ArcId"# $int_name r#"` has exactly
one strong reference.
"#]
#[must_use]
pub fn into_inner(this: Self) -> Option<[<Id $int_name>]> {
Arc::<[<Id $int_name>]>::into_inner(this.0)
}
#[doc = r"
Return the strong count of internal `Arc`.
"]
#[must_use]
pub fn strong_count(&self) -> usize {
Arc::strong_count(&self.0)
}
}
impl Borrow<$int_type> for [<ArcId $int_name>] {
fn borrow(&self) -> &$int_type {
(*self.0).borrow()
}
}
}
};
}
impl_Bag! { u8 U8 }
impl_Bag! { u16 U16 }
impl_Bag! { u32 U32 }
impl_Bag! { u64 U64 }
impl_Bag! { usize Usize }