use std::cell::UnsafeCell;
use std::error::Error;
use std::marker::PhantomPinned;
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::ptr::NonNull;
use crate::borrow_state::BorrowState;
use crate::guards::{InaccessibleGuard, MutGuard, RefGuard};
pub struct GdCell<T>(Pin<Box<GdCellInner<T>>>);
impl<T> GdCell<T> {
pub fn new(value: T) -> Self {
Self(GdCellInner::new(value))
}
pub fn borrow(&self) -> Result<RefGuard<'_, T>, Box<dyn Error>> {
self.0.as_ref().borrow()
}
pub fn borrow_mut(&self) -> Result<MutGuard<'_, T>, Box<dyn Error>> {
self.0.as_ref().borrow_mut()
}
pub fn make_inaccessible<'cell, 'val>(
&'cell self,
original_ref: &'val mut T,
) -> Result<InaccessibleGuard<'val, T>, Box<dyn Error>>
where
'cell: 'val,
{
self.0.as_ref().make_inaccessible(original_ref)
}
pub fn is_currently_bound(&self) -> bool {
self.0.as_ref().is_currently_bound()
}
}
#[derive(Debug)]
pub(crate) struct GdCellInner<T> {
pub(crate) state: UnsafeCell<CellState<T>>,
value: UnsafeCell<T>,
_pin: PhantomPinned,
}
impl<T> GdCellInner<T> {
pub fn new(value: T) -> Pin<Box<Self>> {
let mut uninitialized_cell: Box<MaybeUninit<Self>> = Box::new_uninit();
let uninitialized_cell_ptr = uninitialized_cell.as_mut_ptr();
let value_ptr = unsafe {
let value_ptr = &raw mut (*uninitialized_cell_ptr).value;
value_ptr.write(UnsafeCell::new(value));
value_ptr
};
let value_ref = unsafe { value_ptr.as_ref().unwrap() };
let state_ptr = unsafe { &raw mut (*uninitialized_cell_ptr).state };
unsafe {
state_ptr.write(UnsafeCell::new(CellState::new(value_ref)));
}
Box::into_pin(
unsafe { uninitialized_cell.assume_init() },
)
}
pub fn borrow(self: Pin<&Self>) -> Result<RefGuard<'_, T>, Box<dyn Error>> {
let state = unsafe { self.cell_state_mut() };
state.borrow_state.increment_shared()?;
let value = state.get_ptr();
unsafe { Ok(RefGuard::new(&self.get_ref().state, value)) }
}
pub fn borrow_mut(self: Pin<&Self>) -> Result<MutGuard<'_, T>, Box<dyn Error>> {
let state = unsafe { self.cell_state_mut() };
state.borrow_state.increment_mut()?;
let count = state.borrow_state.mut_count();
let value = state.get_ptr();
unsafe { Ok(MutGuard::new(&self.get_ref().state, count, value)) }
}
pub fn make_inaccessible<'cell: 'val, 'val>(
self: Pin<&'cell Self>,
current_ref: &'val mut T,
) -> Result<InaccessibleGuard<'val, T>, Box<dyn Error>> {
InaccessibleGuard::new(&self.get_ref().state, current_ref)
}
unsafe fn cell_state(&self) -> &CellState<T> {
unsafe { &*self.state.get() }
}
#[allow(clippy::mut_from_ref)]
unsafe fn cell_state_mut(&self) -> &mut CellState<T> {
unsafe { &mut *self.state.get() }
}
pub fn is_currently_bound(self: Pin<&Self>) -> bool {
let state = unsafe { self.cell_state() };
state.borrow_state.shared_count() > 0 || state.borrow_state.mut_count() > 0
}
pub(crate) fn is_currently_mutably_bound(self: Pin<&Self>) -> bool {
unsafe { self.cell_state() }.borrow_state.mut_count() > 0
}
}
#[derive(Debug)]
pub(crate) struct CellState<T> {
pub(crate) borrow_state: BorrowState,
ptr: NonNull<T>,
pub(crate) stack_depth: usize,
}
impl<T> CellState<T> {
fn new(value: &UnsafeCell<T>) -> Self {
Self {
borrow_state: BorrowState::new(),
ptr: NonNull::new(value.get()).unwrap(),
stack_depth: 0,
}
}
pub(crate) fn get_ptr(&self) -> NonNull<T> {
self.ptr
}
pub(crate) fn push_ptr(&mut self, new_ptr: NonNull<T>) -> usize {
self.ptr = new_ptr;
self.stack_depth += 1;
self.stack_depth
}
pub(crate) fn pop_ptr(&mut self, old_ptr: NonNull<T>) -> usize {
self.ptr = old_ptr;
self.stack_depth -= 1;
self.stack_depth
}
#[allow(clippy::mut_from_ref)]
pub(crate) unsafe fn borrow_state(cell_state: &UnsafeCell<Self>) -> &mut BorrowState {
let state = unsafe { cell_state.get().as_mut().unwrap() };
&mut state.borrow_state
}
}
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod test {
use super::*;
#[test]
fn prevent_mut_mut() {
const VAL: i32 = -451431556;
let cell = GdCell::new(VAL);
let guard1 = cell.borrow_mut().unwrap();
let guard2 = cell.borrow_mut();
assert_eq!(*guard1, VAL);
assert!(guard2.is_err());
std::mem::drop(guard1);
}
#[test]
fn prevent_mut_shared() {
const VAL: i32 = 13512;
let cell = GdCell::new(VAL);
let guard1 = cell.borrow_mut().unwrap();
let guard2 = cell.borrow();
assert_eq!(*guard1, VAL);
assert!(guard2.is_err());
std::mem::drop(guard1);
}
#[test]
fn prevent_shared_mut() {
const VAL: i32 = 99;
let cell = GdCell::new(VAL);
let guard1 = cell.borrow().unwrap();
let guard2 = cell.borrow_mut();
assert_eq!(*guard1, VAL);
assert!(guard2.is_err());
std::mem::drop(guard1);
}
#[test]
fn allow_shared_shared() {
const VAL: i32 = 10;
let cell = GdCell::new(VAL);
let guard1 = cell.borrow().unwrap();
let guard2 = cell.borrow().unwrap();
assert_eq!(*guard1, VAL);
assert_eq!(*guard2, VAL);
std::mem::drop(guard1);
}
#[test]
fn allow_inaccessible_mut_mut() {
const VAL: i32 = 23456;
let cell = GdCell::new(VAL);
let mut guard1 = cell.borrow_mut().unwrap();
let mut1 = &mut *guard1;
assert_eq!(*mut1, VAL);
*mut1 = VAL + 50;
let inaccessible_guard = cell.make_inaccessible(mut1).unwrap();
let mut guard2 = cell.borrow_mut().unwrap();
let mut2 = &mut *guard2;
assert_eq!(*mut2, VAL + 50);
*mut2 = VAL - 30;
drop(guard2);
drop(inaccessible_guard);
assert_eq!(*mut1, VAL - 30);
*mut1 = VAL - 5;
drop(guard1);
let guard3 = cell.borrow().unwrap();
assert_eq!(*guard3, VAL - 5);
}
#[test]
fn different_inaccessible() {
const VAL1: i32 = 23456;
const VAL2: i32 = 11111;
let cell1 = GdCell::new(VAL1);
let cell2 = GdCell::new(VAL2);
let mut guard1 = cell1.borrow_mut().unwrap();
let mut1 = &mut *guard1;
assert_eq!(*mut1, VAL1);
*mut1 = VAL1 + 10;
let mut guard2 = cell2.borrow_mut().unwrap();
let mut2 = &mut *guard2;
assert_eq!(*mut2, VAL2);
*mut2 = VAL2 + 10;
let inaccessible_guard = cell1
.make_inaccessible(mut2)
.expect_err("should not allow different references");
drop(inaccessible_guard);
drop(guard1);
drop(guard2);
}
}