use lua_gc::{Gc, HeapRef, Marker, Trace};
#[derive(Debug)]
pub struct GcRef<T: Trace + 'static>(pub Gc<T>);
impl<T: Trace + 'static> GcRef<T> {
pub fn new(value: T) -> Self {
let gc = lua_gc::with_current_heap(|heap| match heap {
Some(heap) => heap.allocate(value),
None => Gc::new_uncollected(value),
});
GcRef(gc)
}
pub fn trace_obj(&self, m: &mut Marker) {
if m.try_visit(self.identity()) {
(**self).trace(m);
}
}
}
impl<T: Trace + 'static> GcRef<T> {
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
Gc::ptr_eq(a.0, b.0)
}
pub fn identity(&self) -> usize {
self.0.identity()
}
pub fn strong_count(&self) -> usize {
1
}
pub fn weak_count(&self) -> usize {
0
}
pub fn downgrade(&self) -> GcWeak<T> {
let identity = self.identity();
let tracked = lua_gc::with_current_heap(|heap| {
heap.and_then(|heap| {
heap.allocation_token(identity)
.map(|token| (HeapRef::from_heap(heap), token))
})
});
let (heap, allocation_token) = match tracked {
Some((heap, token)) => (Some(heap), token),
None => (None, 0),
};
GcWeak {
target: self.0,
identity,
allocation_token,
heap,
}
}
pub fn account_buffer(&self, delta: isize) {
if delta == 0 {
return;
}
lua_gc::with_current_heap(|h| {
if let Some(h) = h {
self.0.account_buffer(h, delta)
}
})
}
}
#[derive(Debug)]
pub struct GcWeak<T: Trace + 'static> {
target: Gc<T>,
identity: usize,
allocation_token: usize,
heap: Option<HeapRef>,
}
impl<T: Trace + 'static> GcWeak<T> {
pub fn upgrade(&self) -> Option<GcRef<T>> {
if let Some(heap) = self.heap {
if !heap.contains_allocation(self.identity, self.allocation_token) {
return None;
}
}
Some(GcRef(self.target))
}
pub fn strong_count(&self) -> usize {
usize::from(self.upgrade().is_some())
}
pub fn identity(&self) -> usize {
self.identity
}
}
impl<T: Trace + 'static> Clone for GcWeak<T> {
fn clone(&self) -> Self {
GcWeak {
target: self.target,
identity: self.identity,
allocation_token: self.allocation_token,
heap: self.heap,
}
}
}
impl<T: Trace + 'static> Clone for GcRef<T> {
fn clone(&self) -> Self {
GcRef(self.0)
}
}
impl<T: Trace + 'static> Copy for GcRef<T> {}
impl<T: Trace + 'static> std::ops::Deref for GcRef<T> {
type Target = T;
fn deref(&self) -> &T {
&*self.0
}
}
impl<T: Trace + 'static> AsRef<T> for GcRef<T> {
fn as_ref(&self) -> &T {
&*self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
struct NoRoots;
impl Trace for NoRoots {
fn trace(&self, _m: &mut Marker) {}
}
#[derive(Debug)]
struct Cell0;
impl Trace for Cell0 {
fn trace(&self, _m: &mut Marker) {}
}
#[test]
fn heap_tracked_weak_refs_stop_upgrading_after_sweep() {
let heap = lua_gc::Heap::new();
heap.unpause();
let _guard = lua_gc::HeapGuard::push(&heap);
let strong = GcRef::new(Cell0);
let weak = strong.downgrade();
assert!(weak.upgrade().is_some());
assert_eq!(weak.strong_count(), 1);
heap.full_collect(&NoRoots);
assert!(weak.upgrade().is_none());
assert_eq!(weak.strong_count(), 0);
}
#[test]
fn uncollected_weak_refs_keep_process_lifetime_behavior() {
let strong = GcRef::new(Cell0);
let weak = strong.downgrade();
assert!(weak.upgrade().is_some());
assert_eq!(weak.strong_count(), 1);
}
}
impl<T: PartialEq + Trace + 'static> PartialEq for GcRef<T> {
fn eq(&self, other: &Self) -> bool {
Gc::ptr_eq(self.0, other.0) || **self == **other
}
}