use crate::internal_prelude::*;
pub(crate) type RawCount = u32;
macro_rules! slotmap_dec_ref { { $slotmap:expr, $ref_:expr, $refcount:expr } => { { {
use $crate::refcount::*;
let key: Ref<_> = $ref_;
let refcount: &mut Count<_> = $refcount;
if let Some(Garbage(key)) = key.dispose(refcount) {
let slotmap: &mut SlotMap<_, _> = $slotmap;
let removed = slotmap.remove(key).expect("entry vanished or wrong key passed?!");
Some(Garbage(removed))
} else {
None
}
} } } }
#[derive(Default, Educe, Ord, PartialOrd, Eq, PartialEq, Deref)]
#[educe(Debug)]
pub(crate) struct Count<K> {
#[deref]
count: RawCount,
#[educe(Debug(ignore))]
marker: PhantomData<K>,
}
#[derive(Deref, Educe)]
#[educe(Debug, Default, Ord, Eq, PartialEq)]
pub(crate) struct Ref<K: slotmap_careful::Key> {
#[deref]
raw_key: K,
#[educe(Debug(ignore))]
marker: PhantomData<K>,
#[educe(Debug(ignore), Ord(ignore), Eq(ignore), PartialEq(ignore))]
#[allow(dead_code)]
bomb: DropBombCondition,
}
impl<K: slotmap_careful::Key> PartialOrd for Ref<K> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
assert_not_impl_any!(DropBombCondition: Clone);
#[derive(Debug, Clone, Error, Eq, PartialEq)]
#[error("memory tracking refcount overflowed")]
pub(crate) struct Overflow;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct Garbage<K>(pub(crate) K);
impl<K> Count<K> {
const fn new_raw(count: RawCount) -> Self {
Count {
count,
marker: PhantomData,
}
}
pub(crate) fn as_usize(&self) -> usize {
let r: u32 = **self;
r as usize
}
}
fn inc_raw(c: &mut RawCount) -> Result<(), Overflow> {
*c = c.checked_add(1).ok_or(Overflow)?;
Ok(())
}
fn dec_raw(c: &mut RawCount) -> Option<Garbage<()>> {
*c = c
.checked_sub(1)
.expect("refcount underflow");
(*c == 0).then_some(Garbage(()))
}
impl<K: slotmap_careful::Key> Ref<K> {
pub(crate) fn new(key: K, count: &mut Count<K>) -> Result<Self, Overflow> {
inc_raw(&mut count.count)?;
Ok(Ref::from_raw(key))
}
pub(crate) fn null() -> Self {
Ref::from_raw(K::null())
}
fn from_raw(raw_key: K) -> Self {
Ref {
raw_key,
marker: PhantomData,
bomb: DropBombCondition::new_armed(),
}
}
pub(crate) fn dispose(mut self, refcount: &mut Count<K>) -> Option<Garbage<K>> {
let was = mem::take(&mut self.raw_key);
assert!(!was.is_null());
dec_raw(&mut refcount.count).map(|_: Garbage<()>| Garbage(was))
}
pub(crate) fn dispose_container_destroyed(mut self) {
let _: K = mem::take(&mut self.raw_key);
}
}
impl<K: slotmap_careful::Key> DefaultExtTake for Ref<K> {}
pub(crate) fn slotmap_insert<K: slotmap_careful::Key, V>(
slotmap: &mut SlotMap<K, V>,
value_maker: impl FnOnce(Count<K>) -> V,
) -> Ref<K> {
let (ref_, ()) = slotmap_try_insert(slotmap, move |refcount| {
Ok::<_, Void>((value_maker(refcount), ()))
})
.void_unwrap();
ref_
}
pub(crate) fn slotmap_try_insert<K: slotmap_careful::Key, V, E, RD>(
slotmap: &mut SlotMap<K, V>,
value_maker: impl FnOnce(Count<K>) -> Result<(V, RD), E>,
) -> Result<(Ref<K>, RD), E> {
let refcount = Count::new_raw(1);
let (value, data) = value_maker(refcount)?;
let raw_key = slotmap.insert(value);
let ref_ = Ref {
raw_key,
marker: PhantomData,
bomb: DropBombCondition::new_armed(),
};
Ok((ref_, data))
}
pub(crate) fn slotmap_remove_early<K: slotmap_careful::Key, V>(
slotmap: &mut SlotMap<K, V>,
key: Ref<K>,
) -> Option<V> {
let r = slotmap.remove(*key);
key.dispose_container_destroyed();
r
}
#[cfg(test)]
impl<K: slotmap_careful::Key> Drop for Ref<K> {
fn drop(&mut self) {
drop_bomb_disarm_assert!(self.bomb, self.raw_key.is_null(),);
}
}
impl From<Overflow> for Error {
fn from(_overflow: Overflow) -> Error {
internal!("reference count overflow in memory tracking (out-of-control subsystem?)").into()
}
}
#[cfg(test)]
mod test {
#![allow(clippy::bool_assert_comparison)]
#![allow(clippy::clone_on_copy)]
#![allow(clippy::dbg_macro)]
#![allow(clippy::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_time_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::string_slice)] #![allow(clippy::let_and_return)]
use super::*;
slotmap_careful::new_key_type! {
struct Id;
}
#[derive(Eq, PartialEq, Debug)]
struct Record {
refcount: Count<Id>,
}
type Map = SlotMap<Id, Record>;
fn setup() -> (Map, Ref<Id>) {
let mut map = Map::default();
let ref_ = slotmap_insert(&mut map, |refcount| Record { refcount });
(map, ref_)
}
#[test]
fn good() {
let (mut map, ref1) = setup();
let ent = map.get_mut(*ref1).unwrap();
let ref2 = Ref::new(*ref1, &mut ent.refcount).unwrap();
let g1: Option<Garbage<Record>> = slotmap_dec_ref!(&mut map, ref1, &mut ent.refcount);
assert_eq!(g1, None);
let ent = map.get_mut(*ref2).unwrap();
let g2: Option<Garbage<Record>> = slotmap_dec_ref!(&mut map, ref2, &mut ent.refcount);
assert!(g2.is_some());
}
#[test]
fn try_insert_fail() {
let mut map = Map::default();
let () = slotmap_try_insert::<_, _, _, String>(&mut map, |_refcount| Err(())).unwrap_err();
}
#[test]
fn drop_ref_without_decrement() {
let (_map, mut ref1) = setup();
let h = ref1.bomb.make_simulated();
drop(ref1);
h.expect_exploded();
}
}