bones_scripting/lua/
freeze.rs

1//! Code by [kyren](https://github.com/kyren) from a to-be-released crate for piccolo.
2
3use std::{cell::RefCell, marker::PhantomData, mem, rc::Rc};
4
5#[derive(Debug, Copy, Clone, Eq, PartialEq)]
6pub enum AccessError {
7    Expired,
8    BadBorrow,
9}
10
11/// Safely erase a lifetime from a value and temporarily store it in a shared handle.
12///
13/// Works by providing only limited access to the held value within an enclosing call to
14/// `FrozenScope::scope`. All cloned handles will refer to the same underlying value with its
15/// lifetime erased.
16///
17/// Useful for passing non-'static values into things that do not understand the Rust lifetime
18/// system and need unrestricted sharing, such as scripting languages.
19pub struct Frozen<F: for<'f> Freeze<'f>> {
20    inner: Rc<RefCell<Option<<F as Freeze<'static>>::Frozen>>>,
21}
22
23pub trait Freeze<'f>: 'static {
24    type Frozen: 'f;
25}
26
27pub struct DynFreeze<T: ?Sized>(PhantomData<T>);
28
29impl<'f, T: ?Sized + for<'a> Freeze<'a>> Freeze<'f> for DynFreeze<T> {
30    type Frozen = <T as Freeze<'f>>::Frozen;
31}
32
33#[macro_export]
34macro_rules! Freeze {
35    ($f:lifetime => $frozen:ty) => {
36        $crate::lua::freeze::DynFreeze::<
37            dyn for<$f> $crate::lua::freeze::Freeze<$f, Frozen = $frozen>,
38        >
39    };
40    ($frozen:ty) => {
41        $crate::lua::freeze::Freeze!['freeze => $frozen]
42    };
43}
44
45pub use crate::Freeze;
46
47impl<F: for<'a> Freeze<'a>> Clone for Frozen<F> {
48    fn clone(&self) -> Self {
49        Self {
50            inner: self.inner.clone(),
51        }
52    }
53}
54
55impl<F: for<'a> Freeze<'a>> Default for Frozen<F> {
56    fn default() -> Self {
57        Self {
58            inner: Rc::new(RefCell::new(None)),
59        }
60    }
61}
62
63impl<F: for<'a> Freeze<'a>> Frozen<F> {
64    /// Creates a new *invalid* `Frozen` handle.
65    pub fn new() -> Self {
66        Self::default()
67    }
68
69    pub fn in_scope<R>(value: <F as Freeze>::Frozen, cb: impl FnOnce(Self) -> R) -> R {
70        let f = Self::new();
71        let p = f.clone();
72        FrozenScope::new().freeze(&f, value).scope(move || cb(p))
73    }
74
75    /// Returns true if this value is currently set by an enclosing `FrozenScope::scope`.
76    pub fn is_valid(&self) -> bool {
77        if let Ok(b) = self.inner.try_borrow() {
78            b.is_some()
79        } else {
80            true
81        }
82    }
83
84    pub fn try_with<R>(
85        &self,
86        f: impl for<'f> FnOnce(&<F as Freeze<'f>>::Frozen) -> R,
87    ) -> Result<R, AccessError> {
88        Ok(f(self
89            .inner
90            .try_borrow()
91            .map_err(|_| AccessError::BadBorrow)?
92            .as_ref()
93            .ok_or(AccessError::Expired)?))
94    }
95
96    /// # Panics
97    /// Panics if this handle is not currently valid or if the held value is already borrowed
98    /// mutably.
99    pub fn with<R>(&self, f: impl for<'f> FnOnce(&<F as Freeze<'f>>::Frozen) -> R) -> R {
100        self.try_with(f).unwrap()
101    }
102
103    pub fn try_with_mut<R>(
104        &self,
105        f: impl for<'f> FnOnce(&mut <F as Freeze<'f>>::Frozen) -> R,
106    ) -> Result<R, AccessError> {
107        Ok(f(self
108            .inner
109            .try_borrow_mut()
110            .map_err(|_| AccessError::BadBorrow)?
111            .as_mut()
112            .ok_or(AccessError::Expired)?))
113    }
114
115    /// # Panics
116    /// Panics if this handle is not currently valid or if the held value is already borrowed.
117    pub fn with_mut<R>(&self, f: impl for<'f> FnOnce(&mut <F as Freeze<'f>>::Frozen) -> R) -> R {
118        self.try_with_mut(f).unwrap()
119    }
120}
121
122/// Struct that enables setting the contents of multiple `Frozen<F>` handles for the body of a
123/// single callback.
124pub struct FrozenScope<D = ()>(D);
125
126impl Default for FrozenScope<()> {
127    fn default() -> Self {
128        FrozenScope(())
129    }
130}
131
132impl FrozenScope<()> {
133    pub fn new() -> Self {
134        Self(())
135    }
136}
137
138impl<D: DropGuard> FrozenScope<D> {
139    /// Sets the given frozen value for the duration of the `FrozenScope::scope` call.
140    pub fn freeze<'h, 'f, F: for<'a> Freeze<'a>>(
141        self,
142        handle: &'h Frozen<F>,
143        value: <F as Freeze<'f>>::Frozen,
144    ) -> FrozenScope<(FreezeGuard<'h, 'f, F>, D)> {
145        FrozenScope((
146            FreezeGuard {
147                value: Some(value),
148                handle,
149            },
150            self.0,
151        ))
152    }
153
154    /// Inside this call, all of the handles set with `FrozenScope::freeze` will be valid and can be
155    /// accessed with `Frozen::with` and `Frozen::with_mut`. The provided handles (and all clones of
156    /// them) are invalidated before this call to `FrozenScope::scope` returns.
157    ///
158    /// # Panics
159    /// Panics if any of the provided handles are already set inside another, outer
160    /// `FrozenScope::scope` call or if any handles were set with `FrozenScope::freeze` more than
161    /// once. The given handles must be used with only one `FrozenScope` at a time.
162    pub fn scope<R>(mut self, cb: impl FnOnce() -> R) -> R {
163        // SAFETY: Safety depends on a few things...
164        //
165        // 1) We turn non-'static values into a 'static ones, outside code should never be able to
166        //    observe the held 'static value, because it lies about the true lifetime.
167        //
168        // 2) The only way to interact with the held 'static value is through `Frozen::[try_]with`
169        //    and `Frozen::[try_]with_mut`, both of which require a callback that works with the
170        //    frozen type for *any* lifetime. This interaction is safe because the callbacks must
171        //    work for any lifetime, so they must work with the lifetime we have erased.
172        //
173        // 3) The 'static `Frozen<F>` handles must have their values unset before the body of
174        //    this function ends because we only know they live for at least the body of this
175        //    function, and we use drop guards for this.
176        unsafe {
177            self.0.set();
178        }
179        let r = cb();
180        drop(self.0);
181        r
182    }
183}
184
185pub trait DropGuard {
186    /// Sets the held `Frozen` handle to the held value.
187    ///
188    /// # Safety
189    /// This is unsafe because the `Frozen` handle can now be used to access the value independent of
190    /// its lifetime and the borrow checker cannot check this.
191    ///
192    /// Implementers of this trait *must* unset the handle's held value when the value is dropped.
193    ///
194    /// Users of this trait *must* drop it before the lifetime of the held value ends.
195    unsafe fn set(&mut self);
196}
197
198impl DropGuard for () {
199    unsafe fn set(&mut self) {}
200}
201
202impl<A: DropGuard, B: DropGuard> DropGuard for (A, B) {
203    unsafe fn set(&mut self) {
204        self.0.set();
205        self.1.set();
206    }
207}
208
209pub struct FreezeGuard<'h, 'f, F: for<'a> Freeze<'a>> {
210    value: Option<<F as Freeze<'f>>::Frozen>,
211    handle: &'h Frozen<F>,
212}
213
214impl<'h, 'f, F: for<'a> Freeze<'a>> Drop for FreezeGuard<'h, 'f, F> {
215    fn drop(&mut self) {
216        if let Ok(mut v) = self.handle.inner.try_borrow_mut() {
217            *v = None;
218        } else {
219            // This should not be possible to trigger safely, because users cannot hold
220            // `Ref` or `RefMut` handles from the inner `RefCell` in the first place,
221            // and `Frozen` does not implement Send so we can't be in the body of
222            // `Frozen::with[_mut]` in another thread. However, if it somehow happens that
223            // we cannot drop the held value, this means that there is a live reference to
224            // it somewhere so we are forced to abort the process.
225            eprintln!("impossible! freeze lock held during drop guard, aborting!");
226            std::process::abort()
227        }
228    }
229}
230
231impl<'h, 'f, F: for<'a> Freeze<'a>> DropGuard for FreezeGuard<'h, 'f, F> {
232    unsafe fn set(&mut self) {
233        assert!(
234            !self.handle.is_valid(),
235            "handle already used in another `FrozenScope::scope` call"
236        );
237        *self.handle.inner.borrow_mut() = Some(mem::transmute::<
238            <F as Freeze<'f>>::Frozen,
239            <F as Freeze<'static>>::Frozen,
240        >(self.value.take().unwrap()));
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn test_freeze_works() {
250        struct F<'a>(&'a i32);
251
252        let i = 4;
253        Frozen::<Freeze![F<'freeze>]>::in_scope(F(&i), |f| {
254            f.with(|f| {
255                assert_eq!(*f.0, 4);
256            });
257        });
258    }
259
260    #[test]
261    fn test_freeze_expires() {
262        struct F<'a>(&'a i32);
263
264        type FrozenF = Frozen<Freeze![F<'freeze>]>;
265
266        let mut outer: Option<FrozenF> = None;
267
268        let i = 4;
269        FrozenF::in_scope(F(&i), |f| {
270            outer = Some(f.clone());
271        });
272
273        assert_eq!(
274            outer.unwrap().try_with(|f| {
275                assert_eq!(*f.0, 4);
276            }),
277            Err(AccessError::Expired)
278        );
279    }
280}