use super::Collectible;
use core::mem::ManuallyDrop;
use core::ops::Deref;
use core::ptr::NonNull;
use portable_atomic::AtomicUsize;
use portable_atomic::Ordering::{self, Relaxed};
pub struct RefCounted<T> {
instance: T,
next_or_refcnt: LinkOrRefCnt,
}
impl<T> RefCounted<T> {
#[inline]
pub(crate) const fn new_shared(t: T) -> Self {
Self {
instance: t,
next_or_refcnt: LinkOrRefCnt::new_shared(),
}
}
#[inline]
pub(crate) const fn new_unique(t: T) -> Self {
Self {
instance: t,
next_or_refcnt: LinkOrRefCnt::new_unique(),
}
}
#[inline]
pub fn try_add_ref(&self, order: Ordering) -> bool {
self.ref_cnt()
.fetch_update(
order,
order,
|r| {
if r % 2 == 1 {
Some(r + 2)
} else {
None
}
},
)
.is_ok()
}
#[inline]
pub fn get_mut_shared(&mut self) -> Option<&mut T> {
if self.ref_cnt().load(Relaxed) == 1 {
Some(&mut self.instance)
} else {
None
}
}
#[inline]
pub fn get_mut_unique(&mut self) -> &mut T {
debug_assert_eq!(self.ref_cnt().load(Relaxed), 0);
&mut self.instance
}
#[inline]
pub fn add_ref(&self) {
let mut current = self.ref_cnt().load(Relaxed);
loop {
debug_assert_eq!(current % 2, 1);
debug_assert!(current <= usize::MAX - 2, "reference count overflow");
match self
.ref_cnt()
.compare_exchange_weak(current, current + 2, Relaxed, Relaxed)
{
Ok(_) => break,
Err(actual) => {
current = actual;
}
}
}
}
#[inline]
pub fn drop_ref(&self) -> bool {
let mut current = self.ref_cnt().load(Relaxed);
loop {
debug_assert_ne!(current, 0);
let new = if current <= 1 { 0 } else { current - 2 };
match self
.ref_cnt()
.compare_exchange_weak(current, new, Relaxed, Relaxed)
{
Ok(_) => break,
Err(actual) => {
current = actual;
}
}
}
current == 1
}
#[inline]
pub fn ref_cnt(&self) -> &AtomicUsize {
unsafe { &self.next_or_refcnt.refcnt.0 }
}
#[inline]
pub fn as_collectible(&self) -> &dyn Collectible {
self
}
}
impl<T> Deref for RefCounted<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.instance
}
}
impl<T> Collectible for RefCounted<T> {
#[inline]
fn next_ptr_mut(&mut self) -> &mut Option<NonNull<dyn Collectible>> {
unsafe { &mut self.next_or_refcnt.next }
}
}
pub union LinkOrRefCnt {
next: Option<NonNull<dyn Collectible>>,
refcnt: ManuallyDrop<(AtomicUsize, usize)>,
}
impl LinkOrRefCnt {
#[inline]
const fn new_shared() -> Self {
LinkOrRefCnt {
refcnt: ManuallyDrop::new((AtomicUsize::new(1), 0)),
}
}
#[inline]
const fn new_unique() -> Self {
LinkOrRefCnt {
refcnt: ManuallyDrop::new((AtomicUsize::new(0), 0)),
}
}
}