use std::marker::PhantomData;
use crate::{GcNode, GcRef, heap::GcHeap};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct GcWeakRawId(u32);
impl GcWeakRawId {
pub(crate) const NULL: Self = Self(0);
pub const fn index(&self) -> u16 {
(self.0 >> 16) as u16
}
pub const fn version(&self) -> u16 {
self.0 as u16
}
pub const fn is_null(&self) -> bool {
self.version() == 0
}
}
#[derive(PartialEq)]
#[repr(transparent)]
pub struct GcWeak<T: GcNode> {
pub(crate) weak_id: GcWeakRawId,
pub(crate) _marker: PhantomData<T>,
}
impl<T: GcNode> Clone for GcWeak<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: GcNode> Copy for GcWeak<T> {}
impl<T: GcNode> Default for GcWeak<T> {
fn default() -> Self {
Self {
weak_id: GcWeakRawId::NULL,
_marker: PhantomData,
}
}
}
impl<T: GcNode> std::fmt::Debug for GcWeak<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "GcWeak({}#{})", self.index(), self.version())
}
}
impl<T: GcNode> GcWeak<T> {
pub(crate) fn new(index: u16, version: u16) -> Self {
debug_assert!(version > 0);
Self {
weak_id: GcWeakRawId(((index as u32) << 16) | (version as u32)),
_marker: PhantomData,
}
}
pub(crate) fn from_id(weak_id: GcWeakRawId) -> Self {
Self {
weak_id,
_marker: PhantomData,
}
}
#[inline(always)]
pub fn index(&self) -> u16 {
self.weak_id.index()
}
#[inline(always)]
pub fn version(&self) -> u16 {
self.weak_id.version()
}
#[inline(always)]
pub fn upgrade(&self, heap: &GcHeap) -> Option<GcRef<T>> {
heap.upgrade(self)
}
}
impl GcHeap {
pub fn downgrade<T: GcNode>(&mut self, gc_ref: &GcRef<T>) -> GcWeak<T> {
let node = unsafe {
let mut h = gc_ref.head_ptr;
h.as_mut()
};
if !node.weak_id.is_null() {
#[cfg(debug_assertions)]
{
debug_assert!((node.weak_id.index() as usize) < self.weak_slots.len());
let (ver, ptr) = self.weak_slots[node.weak_id.index() as usize];
debug_assert!(
ver == node.weak_id.version() && ptr.is_some_and(|p| p == gc_ref.head_ptr)
);
}
GcWeak::from_id(node.weak_id)
} else {
if self.weak_slots.len() == u16::MAX as usize {
panic!("too may weakrefs");
}
let i = self
.weak_slots
.iter()
.position(|(_, slot)| slot.is_none())
.unwrap_or_else(|| {
let n = self.weak_slots.len();
self.weak_slots.push((0, None));
n
});
unsafe {
let curr_ver = self.weak_slots.get_unchecked(i).0;
let version = if curr_ver == u16::MAX {
1
} else {
curr_ver + 1
};
let weak = GcWeak::new(i as _, version);
*self.weak_slots.get_unchecked_mut(i) = (version, Some(gc_ref.head_ptr));
node.weak_id = weak.weak_id;
weak
}
}
}
pub fn upgrade<T: GcNode>(&self, weak_ref: &GcWeak<T>) -> Option<GcRef<T>> {
if !weak_ref.weak_id.is_null() {
self.weak_slots
.get(weak_ref.index() as usize)
.and_then(|(version, node)| {
if *version == weak_ref.version() {
*node
} else {
None
}
})
.map(|ptr| GcRef {
head_ptr: ptr,
_marker: PhantomData,
})
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use crate::{GcTrace, GcTraceCtx, gc_type_register};
use super::*;
#[derive(Debug, Default, PartialEq, Eq)]
struct TestData1;
impl GcTrace for TestData1 {
fn trace(&self, _: &mut GcTraceCtx) {}
}
#[derive(Debug, Default, PartialEq, Eq)]
struct TestData2;
impl GcTrace for TestData2 {
fn trace(&self, _: &mut GcTraceCtx) {}
}
gc_type_register! {
TestData1;
TestData2;
}
#[test]
fn test_weak_ref_clone_and_copy() {
let weak1 = GcWeak::<TestData1>::new(10, 1);
let weak2 = weak1;
assert_eq!(weak1.index(), weak2.index());
let weak3 = weak1;
let weak4 = weak1; assert_eq!(weak3.index(), weak4.index());
}
#[test]
fn test_weak_ref_equality() {
let weak1 = GcWeak::<TestData1>::new(5, 1);
let weak2 = GcWeak::<TestData1>::new(5, 1);
let weak3 = GcWeak::<TestData1>::new(10, 1);
assert_eq!(weak1, weak2);
assert_ne!(weak1, weak3);
}
#[test]
fn test_weak_ref_upgrade_interface() {
let heap = GcHeap::new(&GC_TYPE_REGISTRY);
let weak_ref = GcWeak::<TestData1>::new(0, 1);
let result = weak_ref.upgrade(&heap);
debug_assert!(result.is_none());
}
#[test]
fn test_weak_ref_type_safety() {
let weak_i32 = GcWeak::<TestData1>::new(1, 1);
let weak_string = GcWeak::<TestData2>::new(1, 1);
debug_assert_eq!(weak_i32.index(), weak_string.index());
debug_assert_eq!(
std::any::TypeId::of::<GcWeak<TestData1>>(),
std::any::TypeId::of::<GcWeak<TestData1>>()
);
debug_assert_ne!(
std::any::TypeId::of::<GcWeak<TestData1>>(),
std::any::TypeId::of::<GcWeak<TestData2>>()
);
}
#[test]
fn test_weak_ref_edge_cases() {
let weak_max = GcWeak::<TestData1>::new(u16::MAX, 1);
debug_assert_eq!(weak_max.index(), u16::MAX);
let weak_min = GcWeak::<TestData1>::new(0, 1);
debug_assert_eq!(weak_min.index(), 0);
let weak_i32 = GcWeak::<TestData1>::new(5, 1);
let weak_string = GcWeak::<TestData2>::new(5, 1);
debug_assert_eq!(weak_i32.index(), weak_string.index());
}
#[test]
fn test_weak_ref_serialization_compatibility() {
let _weak_ref = GcWeak::<TestData1>::new(42, 1);
assert_eq!(
std::mem::size_of::<GcWeak<TestData1>>(),
std::mem::size_of::<u32>()
);
assert_eq!(
std::mem::align_of::<GcWeak<TestData1>>(),
std::mem::align_of::<u32>()
);
}
}