use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum AccessError {
Expired,
BadBorrow,
}
pub struct Frozen<F: for<'f> Freeze<'f>> {
inner: Rc<RefCell<Option<<F as Freeze<'static>>::Frozen>>>,
}
pub trait Freeze<'f>: 'static {
type Frozen: 'f;
}
pub struct DynFreeze<T: ?Sized>(PhantomData<T>);
impl<'f, T: ?Sized + for<'a> Freeze<'a>> Freeze<'f> for DynFreeze<T> {
type Frozen = <T as Freeze<'f>>::Frozen;
}
#[macro_export]
macro_rules! Freeze {
($f:lifetime => $frozen:ty) => {
$crate::lua::freeze::DynFreeze::<
dyn for<$f> $crate::lua::freeze::Freeze<$f, Frozen = $frozen>,
>
};
($frozen:ty) => {
$crate::lua::freeze::Freeze!['freeze => $frozen]
};
}
pub use crate::Freeze;
impl<F: for<'a> Freeze<'a>> Clone for Frozen<F> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<F: for<'a> Freeze<'a>> Default for Frozen<F> {
fn default() -> Self {
Self {
inner: Rc::new(RefCell::new(None)),
}
}
}
impl<F: for<'a> Freeze<'a>> Frozen<F> {
pub fn new() -> Self {
Self::default()
}
pub fn in_scope<R>(value: <F as Freeze>::Frozen, cb: impl FnOnce(Self) -> R) -> R {
let f = Self::new();
let p = f.clone();
FrozenScope::new().freeze(&f, value).scope(move || cb(p))
}
pub fn is_valid(&self) -> bool {
if let Ok(b) = self.inner.try_borrow() {
b.is_some()
} else {
true
}
}
pub fn try_with<R>(
&self,
f: impl for<'f> FnOnce(&<F as Freeze<'f>>::Frozen) -> R,
) -> Result<R, AccessError> {
Ok(f(self
.inner
.try_borrow()
.map_err(|_| AccessError::BadBorrow)?
.as_ref()
.ok_or(AccessError::Expired)?))
}
pub fn with<R>(&self, f: impl for<'f> FnOnce(&<F as Freeze<'f>>::Frozen) -> R) -> R {
self.try_with(f).unwrap()
}
pub fn try_with_mut<R>(
&self,
f: impl for<'f> FnOnce(&mut <F as Freeze<'f>>::Frozen) -> R,
) -> Result<R, AccessError> {
Ok(f(self
.inner
.try_borrow_mut()
.map_err(|_| AccessError::BadBorrow)?
.as_mut()
.ok_or(AccessError::Expired)?))
}
pub fn with_mut<R>(&self, f: impl for<'f> FnOnce(&mut <F as Freeze<'f>>::Frozen) -> R) -> R {
self.try_with_mut(f).unwrap()
}
}
pub struct FrozenScope<D = ()>(D);
impl Default for FrozenScope<()> {
fn default() -> Self {
FrozenScope(())
}
}
impl FrozenScope<()> {
pub fn new() -> Self {
Self(())
}
}
impl<D: DropGuard> FrozenScope<D> {
pub fn freeze<'h, 'f, F: for<'a> Freeze<'a>>(
self,
handle: &'h Frozen<F>,
value: <F as Freeze<'f>>::Frozen,
) -> FrozenScope<(FreezeGuard<'h, 'f, F>, D)> {
FrozenScope((
FreezeGuard {
value: Some(value),
handle,
},
self.0,
))
}
pub fn scope<R>(mut self, cb: impl FnOnce() -> R) -> R {
unsafe {
self.0.set();
}
let r = cb();
drop(self.0);
r
}
}
pub trait DropGuard {
unsafe fn set(&mut self);
}
impl DropGuard for () {
unsafe fn set(&mut self) {}
}
impl<A: DropGuard, B: DropGuard> DropGuard for (A, B) {
unsafe fn set(&mut self) {
self.0.set();
self.1.set();
}
}
pub struct FreezeGuard<'h, 'f, F: for<'a> Freeze<'a>> {
value: Option<<F as Freeze<'f>>::Frozen>,
handle: &'h Frozen<F>,
}
impl<'h, 'f, F: for<'a> Freeze<'a>> Drop for FreezeGuard<'h, 'f, F> {
fn drop(&mut self) {
if let Ok(mut v) = self.handle.inner.try_borrow_mut() {
*v = None;
} else {
eprintln!("impossible! freeze lock held during drop guard, aborting!");
std::process::abort()
}
}
}
impl<'h, 'f, F: for<'a> Freeze<'a>> DropGuard for FreezeGuard<'h, 'f, F> {
unsafe fn set(&mut self) {
assert!(
!self.handle.is_valid(),
"handle already used in another `FrozenScope::scope` call"
);
*self.handle.inner.borrow_mut() = Some(mem::transmute::<
<F as Freeze<'f>>::Frozen,
<F as Freeze<'static>>::Frozen,
>(self.value.take().unwrap()));
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_freeze_works() {
struct F<'a>(&'a i32);
let i = 4;
Frozen::<Freeze![F<'freeze>]>::in_scope(F(&i), |f| {
f.with(|f| {
assert_eq!(*f.0, 4);
});
});
}
#[test]
fn test_freeze_expires() {
struct F<'a>(&'a i32);
type FrozenF = Frozen<Freeze![F<'freeze>]>;
let mut outer: Option<FrozenF> = None;
let i = 4;
FrozenF::in_scope(F(&i), |f| {
outer = Some(f.clone());
});
assert_eq!(
outer.unwrap().try_with(|f| {
assert_eq!(*f.0, 4);
}),
Err(AccessError::Expired)
);
}
}