use crate::GcPtr;
use crate::errors::{Error, ErrorEnum};
use crate::generation::edge::Edge;
use crate::generation::{GenerationPtr, ObjectState};
use crate::object::{DynObjectPtr, ObjectIntf, ObjectPtr};
use crate::prelude::GcPtrEq;
use std::fmt;
use std::ops::Deref;
use std::pin::Pin;
use std::ptr;
#[cfg(feature = "multi_thread")]
use super::SingleOrMultiThreadPtr;
#[cfg(not(feature = "single_generation"))]
use crate::generation::Generation;
#[cfg(feature = "multi_thread")]
use crate::object::{MTObjectIntf, MTObjectPtr};
#[cfg(feature = "multi_thread")]
use crate::sync::{GcMtMemberPtr, GcMtPtr};
pub struct GcMemberPtr<T>
where
T: 'static,
{
pub(super) origin: Pin<DynObjectPtr>,
#[cfg(not(feature = "multi_thread"))]
pub(super) ptr: Pin<ObjectPtr<T>>,
#[cfg(feature = "multi_thread")]
pub(super) ptr: SingleOrMultiThreadPtr<T>,
edge: Pin<Box<Edge>>,
}
impl<T> GcMemberPtr<T>
where
T: 'static,
{
unsafe fn new_ptr_st(origin: Pin<DynObjectPtr>, sp_ptr: Pin<ObjectPtr<T>>) -> Self {
let edge = Box::pin(Edge::new(unsafe {
Pin::new_unchecked(sp_ptr.get_control_block())
}));
let origin_generation = origin.get_generation_ptr();
let sp_ptr_generation = sp_ptr.get_generation_ptr();
#[cfg(feature = "single_generation")]
{
assert!(GenerationPtr::ptr_eq(
&origin_generation,
&sp_ptr_generation
));
origin.get_control_block().register_edge(edge.as_ref());
sp_ptr.get_control_block().refcount_dec_no_gc();
}
#[cfg(not(feature = "single_generation"))]
if GenerationPtr::ptr_eq(&origin_generation, &sp_ptr_generation) {
origin.get_control_block().register_edge(edge.as_ref());
sp_ptr.get_control_block().refcount_dec_no_gc();
} else if origin_generation.id < sp_ptr_generation.id {
origin.get_control_block().register_edge(edge.as_ref());
} else {
let merged_generation = Generation::merge(sp_ptr_generation, origin_generation);
assert!(GenerationPtr::ptr_eq(
&merged_generation,
&origin.get_generation_ptr()
));
assert!(GenerationPtr::ptr_eq(
&merged_generation,
&sp_ptr.get_generation_ptr()
));
origin.get_control_block().register_edge(edge.as_ref());
sp_ptr.get_control_block().refcount_dec_no_gc();
}
GcMemberPtr {
origin,
#[cfg(not(feature = "multi_thread"))]
ptr: sp_ptr,
#[cfg(feature = "multi_thread")]
ptr: sp_ptr.into(),
edge,
}
}
#[cfg(feature = "multi_thread")]
unsafe fn new_ptr_mt(origin: Pin<DynObjectPtr>, sp_ptr: Pin<MTObjectPtr<T>>) -> Self {
let edge = Box::pin(Edge::new_mt(unsafe {
Pin::new_unchecked(sp_ptr.get_control_block())
}));
origin.get_control_block().register_edge(edge.as_ref());
GcMemberPtr {
origin,
ptr: sp_ptr.into(),
edge,
}
}
pub(crate) fn new_ptr(origin: Pin<DynObjectPtr>, sp: GcPtr<T>) -> Self {
unsafe {
let sp_ptr = GcPtr::release(sp);
#[cfg(not(feature = "multi_thread"))]
return Self::new_ptr_st(origin, sp_ptr);
#[cfg(feature = "multi_thread")]
return match sp_ptr {
SingleOrMultiThreadPtr::SingleThread(sp_ptr) => Self::new_ptr_st(origin, sp_ptr),
SingleOrMultiThreadPtr::MultiThread(sp_ptr) => Self::new_ptr_mt(origin, sp_ptr),
};
}
}
#[inline]
pub fn as_ptr(&self) -> GcPtr<T> {
if self.origin.object_state() == ObjectState::Expired {
panic!("cannot dereference member pointers on targets that are unreachable");
}
#[cfg(not(feature = "multi_thread"))]
return {
self.ptr.get_control_block().refcount_inc();
unsafe { GcPtr::new_from_raw(self.ptr.clone()) }
};
#[cfg(feature = "multi_thread")]
return {
match &self.ptr {
SingleOrMultiThreadPtr::SingleThread(ptr) => {
ptr.get_control_block().refcount_inc();
unsafe { GcPtr::new_from_raw(ptr.clone()) }
}
SingleOrMultiThreadPtr::MultiThread(ptr) => {
ptr.get_control_block().refcount_inc();
unsafe { GcPtr::new_from_raw_mt(ptr.clone()) }
}
}
};
}
#[inline]
pub fn try_deref(&self) -> Result<&T, Error> {
if self.origin.object_state() == ObjectState::Expired {
return Err(Error::new(ErrorEnum::OriginExpired(
self.origin.created_backtrace().clone(),
)));
}
#[cfg(not(feature = "multi_thread"))]
return Ok(self.ptr.get_data());
#[cfg(feature = "multi_thread")]
return match &self.ptr {
SingleOrMultiThreadPtr::SingleThread(ptr) => Ok(ptr.get_data()),
SingleOrMultiThreadPtr::MultiThread(ptr) => Ok(ptr.get_data()),
};
}
#[inline]
pub fn try_as_ptr(&self) -> Result<GcPtr<T>, Error> {
if self.origin.object_state() == ObjectState::Expired {
return Err(Error::new(ErrorEnum::OriginExpired(
self.origin.created_backtrace().clone(),
)));
}
#[cfg(not(feature = "multi_thread"))]
return {
self.ptr.get_control_block().refcount_inc();
Ok(unsafe { GcPtr::new_from_raw(self.ptr.clone()) })
};
#[cfg(feature = "multi_thread")]
return {
match &self.ptr {
SingleOrMultiThreadPtr::SingleThread(ptr) => {
ptr.get_control_block().refcount_inc();
Ok(unsafe { GcPtr::new_from_raw(ptr.clone()) })
}
SingleOrMultiThreadPtr::MultiThread(ptr) => {
ptr.get_control_block().refcount_inc();
Ok(unsafe { GcPtr::new_from_raw_mt(ptr.clone()) })
}
}
};
}
}
#[cfg(feature = "multi_thread")]
impl<T> GcMemberPtr<T>
where
T: 'static + Send + Sync,
{
pub(crate) fn new_mt_ptr(origin: Pin<DynObjectPtr>, sp: GcMtPtr<T>) -> Self {
unsafe { Self::new_ptr_mt(origin, GcMtPtr::release(sp)) }
}
}
impl<T> GcPtrEq<GcPtr<T>> for GcMemberPtr<T>
where
T: 'static,
{
#[inline]
fn ptr_eq(this: &Self, other: &GcPtr<T>) -> bool {
GcPtr::ptr_eq(other, this)
}
}
#[cfg(feature = "multi_thread")]
impl<T> GcPtrEq<GcMtPtr<T>> for GcMemberPtr<T>
where
T: 'static + Send + Sync,
{
#[inline]
fn ptr_eq(this: &Self, other: &GcMtPtr<T>) -> bool {
GcMtPtr::ptr_eq(other, this)
}
}
impl<T> GcPtrEq<GcMemberPtr<T>> for GcMemberPtr<T>
where
T: 'static,
{
#[inline]
fn ptr_eq(this: &Self, other: &GcMemberPtr<T>) -> bool {
#[cfg(not(feature = "multi_thread"))]
return ptr::eq(&*this.ptr, &*other.ptr);
#[cfg(feature = "multi_thread")]
return match (&this.ptr, &other.ptr) {
(
SingleOrMultiThreadPtr::SingleThread(this_ptr),
SingleOrMultiThreadPtr::SingleThread(other_ptr),
) => ptr::eq(&**this_ptr, &**other_ptr),
(SingleOrMultiThreadPtr::SingleThread(_), SingleOrMultiThreadPtr::MultiThread(_)) => {
false
}
(SingleOrMultiThreadPtr::MultiThread(_), SingleOrMultiThreadPtr::SingleThread(_)) => {
false
}
(
SingleOrMultiThreadPtr::MultiThread(this_ptr),
SingleOrMultiThreadPtr::MultiThread(other_ptr),
) => ptr::eq(&**this_ptr, &**other_ptr),
};
}
}
#[cfg(feature = "multi_thread")]
impl<T> GcPtrEq<GcMtMemberPtr<T>> for GcMemberPtr<T>
where
T: 'static + Send + Sync,
{
#[inline]
fn ptr_eq(this: &Self, other: &GcMtMemberPtr<T>) -> bool {
match &this.ptr {
SingleOrMultiThreadPtr::MultiThread(this_ptr) => ptr::eq(&**this_ptr, &*other.ptr),
_ => false,
}
}
}
#[cfg(feature = "weak_pointer")]
impl<T> GcPtrEq<crate::Weak<T>> for GcMemberPtr<T>
where
T: 'static,
{
#[inline]
fn ptr_eq(this: &Self, other: &crate::Weak<T>) -> bool {
#[cfg(not(feature = "multi_thread"))]
return other
.ptr
.as_ref()
.map(|other_ptr| ptr::eq(&*this.ptr, &**other_ptr))
.unwrap_or(false);
#[cfg(feature = "multi_thread")]
return other
.ptr
.as_ref()
.map(|other_ptr| match (&this.ptr, other_ptr) {
(
SingleOrMultiThreadPtr::SingleThread(this_ptr),
SingleOrMultiThreadPtr::SingleThread(other_ptr),
) => ptr::eq(&**this_ptr, &**other_ptr),
(
SingleOrMultiThreadPtr::SingleThread(_),
SingleOrMultiThreadPtr::MultiThread(_),
) => false,
(
SingleOrMultiThreadPtr::MultiThread(_),
SingleOrMultiThreadPtr::SingleThread(_),
) => false,
(
SingleOrMultiThreadPtr::MultiThread(this_ptr),
SingleOrMultiThreadPtr::MultiThread(other_ptr),
) => ptr::eq(&**this_ptr, &**other_ptr),
})
.unwrap_or(false);
}
}
#[cfg(all(feature = "multi_thread", feature = "weak_pointer"))]
impl<T> GcPtrEq<crate::sync::Weak<T>> for GcMemberPtr<T>
where
T: 'static + Send + Sync,
{
#[inline]
fn ptr_eq(this: &Self, other: &crate::sync::Weak<T>) -> bool {
other
.ptr
.as_ref()
.map(|other_ptr| match &this.ptr {
SingleOrMultiThreadPtr::MultiThread(this_ptr) => ptr::eq(&**this_ptr, &**other_ptr),
_ => false,
})
.unwrap_or(false)
}
}
impl<T> Drop for GcMemberPtr<T>
where
T: 'static,
{
#[cfg(not(feature = "multi_thread"))]
#[allow(
clippy::missing_inline_in_public_items,
reason = "The drop of a member function does a lot of complicated things. There's probably very little benefit to inlining this, and not inlining it hopefully allows for easier debugging."
)]
fn drop(&mut self) {
let origin_generation = self.origin.get_generation_ptr();
let ptr_generation = self.ptr.get_generation_ptr();
self.origin
.get_control_block()
.deregister_edge(self.edge.as_ref().get_ref());
#[cfg(feature = "single_generation")]
{
assert!(GenerationPtr::ptr_eq(&origin_generation, &ptr_generation));
self.ptr.get_control_block().gc_if_zero_refs();
}
#[cfg(not(feature = "single_generation"))]
if GenerationPtr::ptr_eq(&origin_generation, &ptr_generation) {
self.ptr.get_control_block().gc_if_zero_refs();
} else {
assert!(origin_generation.id < ptr_generation.id);
self.ptr.get_control_block().refcount_dec();
}
}
#[cfg(feature = "multi_thread")]
#[allow(
clippy::missing_inline_in_public_items,
reason = "The drop of a member function does a lot of complicated things. There's probably very little benefit to inlining this, and not inlining it hopefully allows for easier debugging."
)]
fn drop(&mut self) {
match &self.ptr {
SingleOrMultiThreadPtr::SingleThread(ptr) => {
let origin_generation = self.origin.get_generation_ptr();
let ptr_generation = ptr.get_generation_ptr();
self.origin
.get_control_block()
.deregister_edge(self.edge.as_ref().get_ref());
#[cfg(feature = "single_generation")]
{
assert!(GenerationPtr::ptr_eq(&origin_generation, &ptr_generation));
ptr.get_control_block().gc_if_zero_refs();
}
#[cfg(not(feature = "single_generation"))]
if GenerationPtr::ptr_eq(&origin_generation, &ptr_generation) {
ptr.get_control_block().gc_if_zero_refs();
} else {
assert!(origin_generation.id < ptr_generation.id);
ptr.get_control_block().refcount_dec();
}
}
SingleOrMultiThreadPtr::MultiThread(ptr) => {
self.origin
.get_control_block()
.deregister_edge(self.edge.as_ref().get_ref());
ptr.get_control_block().refcount_dec();
}
};
}
}
impl<T> Deref for GcMemberPtr<T>
where
T: 'static,
{
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
if self.origin.object_state() == ObjectState::Expired {
panic!("cannot dereference member pointers on targets that are unreachable");
}
#[cfg(not(feature = "multi_thread"))]
return self.ptr.get_data();
#[cfg(feature = "multi_thread")]
return match &self.ptr {
SingleOrMultiThreadPtr::SingleThread(ptr) => ptr.get_data(),
SingleOrMultiThreadPtr::MultiThread(ptr) => ptr.get_data(),
};
}
}
impl<T> fmt::Debug for GcMemberPtr<T>
where
T: 'static + Send + Sync + fmt::Debug,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use super::GcMemberPtr;
use crate::prelude::GcMemberPtrNew;
use crate::{GcPtr, Metadata};
use std::sync::{Arc, Mutex, RwLock};
#[derive(Debug)]
struct Bla {
n: Arc<Mutex<i32>>,
}
#[derive(Debug)]
struct BlaParent {
_bla: GcMemberPtr<Bla>,
}
impl Bla {
fn new(n: Arc<Mutex<i32>>) -> GcPtr<Bla> {
*n.lock().unwrap() += 1;
GcPtr::new(|_| Bla { n })
}
}
impl Drop for Bla {
fn drop(&mut self) {
*self.n.lock().unwrap() -= 1;
}
}
impl BlaParent {
fn new(bla: GcPtr<Bla>) -> GcPtr<BlaParent> {
GcPtr::new(|metadata| BlaParent {
_bla: metadata.new_pointer(bla),
})
}
}
struct Cycle {
n: Arc<Mutex<i32>>,
cptr: RwLock<Option<GcMemberPtr<Cycle>>>,
metadata: Metadata,
}
impl Cycle {
fn new(n: Arc<Mutex<i32>>) -> GcPtr<Cycle> {
*n.lock().unwrap() += 1;
GcPtr::new(|metadata| Cycle {
n,
cptr: RwLock::new(None),
metadata,
})
}
fn assign(&self, ptr: GcPtr<Cycle>) {
*self.cptr.write().unwrap() = Some(self.metadata.new_pointer(ptr));
}
}
impl Drop for Cycle {
fn drop(&mut self) {
*self.n.lock().unwrap() -= 1;
}
}
#[test]
fn create_pointer() {
let n = Arc::new(Mutex::new(0));
let bla = Bla::new(n.clone());
let bla_parent = BlaParent::new(bla.clone());
assert_eq!(*n.lock().unwrap(), 1);
drop(bla);
assert_eq!(
*n.lock().unwrap(),
1,
"`bla` is to remain live, because `bla_parent` has a member-pointer pointing at it"
);
drop(bla_parent);
assert_eq!(*n.lock().unwrap(), 0);
}
#[test]
fn create_cycle() {
let n = Arc::new(Mutex::new(0));
let p = Cycle::new(n.clone());
let q = Cycle::new(n.clone());
assert_eq!(*n.lock().unwrap(), 2);
p.assign(q.clone());
q.assign(p.clone());
assert_eq!(*n.lock().unwrap(), 2);
drop(p);
assert_eq!(
*n.lock().unwrap(),
2,
"`cycle p` is to remain live, because `cycle q` has a member-pointer pointing at it"
);
let p = q.cptr.read().unwrap().as_ref().unwrap().as_ptr();
drop(q);
assert_eq!(
*n.lock().unwrap(),
2,
"`cycle q` is to remain live, because `cycle p` has a member-pointer pointing at it"
);
drop(p);
assert_eq!(*n.lock().unwrap(), 0);
}
}