Skip to main content

luaur_rt/
app_data.rs

1//! Per-VM **application data** — a typed, borrow-checked side store keyed by
2//! Rust `TypeId`. Mirrors `mlua::Lua`'s app-data surface (`set_app_data`,
3//! `app_data_ref`, `app_data_mut`, `remove_app_data`, and the `try_*` variants).
4//!
5//! Each `Lua` instance has its own store. Values are kept behind a per-entry
6//! [`RefCell`], so a `&T` ([`AppDataRef`]) and a `&mut T` ([`AppDataRefMut`])
7//! of **different** types can coexist (the usual aliasing rules apply only
8//! within a single type). A VM-wide borrow counter (matching mlua's `AppData`)
9//! additionally makes *any* outstanding borrow block `set_app_data` /
10//! `remove_app_data` of *any* type, so the container is never mutated while a
11//! guard is live.
12//!
13//! The store lives in a thread-local map keyed by the VM's global-state pointer
14//! (the same pattern `luau_ext` uses for the per-VM compiler), since `LuaInner`
15//! itself is shared immutably behind an `XRc`.
16
17use std::any::{Any, TypeId};
18use std::cell::{Cell, RefCell};
19use std::collections::HashMap;
20use std::ops::{Deref, DerefMut};
21use std::rc::Rc;
22
23use crate::error::{Error, Result};
24use crate::state::Lua;
25use crate::sys::lua_State;
26
27/// One entry: a value behind a `RefCell` so per-type borrows can be tracked,
28/// shared via `Rc` so a returned guard can outlive a borrow of the outer map.
29type Entry = Rc<RefCell<Box<dyn Any>>>;
30
31/// Per-VM store: the entries plus a VM-wide outstanding-borrow counter (shared
32/// via `Rc<Cell<usize>>` so a live guard can decrement it on drop).
33#[derive(Default)]
34struct Store {
35    entries: HashMap<TypeId, Entry>,
36    borrow: Rc<Cell<usize>>,
37}
38
39thread_local! {
40    /// Per-VM application-data store, keyed by global-state pointer.
41    static APP_DATA: RefCell<HashMap<*mut core::ffi::c_void, Store>> =
42        RefCell::new(HashMap::new());
43}
44
45unsafe fn vm_key(state: *mut lua_State) -> *mut core::ffi::c_void {
46    unsafe { (*state).global as *mut core::ffi::c_void }
47}
48
49/// An immutable borrow of an application-data value of type `T`. Mirrors
50/// `mlua::AppDataRef`.
51pub struct AppDataRef<T: 'static> {
52    // Hold the `Rc<RefCell>` alive and the `Ref` borrow open for as long as the
53    // guard lives. The `'static` `Ref` is sound because the `_owner` `Rc` we
54    // also hold keeps the `RefCell` alive (both drop together).
55    _owner: Entry,
56    guard: std::cell::Ref<'static, Box<dyn Any>>,
57    borrow: Rc<Cell<usize>>,
58    _marker: std::marker::PhantomData<T>,
59}
60
61impl<T: 'static> Drop for AppDataRef<T> {
62    fn drop(&mut self) {
63        self.borrow.set(self.borrow.get().saturating_sub(1));
64    }
65}
66
67impl<T: 'static> Deref for AppDataRef<T> {
68    type Target = T;
69    fn deref(&self) -> &T {
70        self.guard
71            .downcast_ref::<T>()
72            .expect("app data type mismatch")
73    }
74}
75
76impl<T: std::fmt::Debug + 'static> std::fmt::Debug for AppDataRef<T> {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78        (**self).fmt(f)
79    }
80}
81
82impl<T: std::fmt::Display + 'static> std::fmt::Display for AppDataRef<T> {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        (**self).fmt(f)
85    }
86}
87
88impl<T: PartialEq + 'static> PartialEq<T> for AppDataRef<T> {
89    fn eq(&self, other: &T) -> bool {
90        (**self) == *other
91    }
92}
93
94/// A mutable borrow of an application-data value of type `T`. Mirrors
95/// `mlua::AppDataRefMut`.
96pub struct AppDataRefMut<T: 'static> {
97    _owner: Entry,
98    guard: std::cell::RefMut<'static, Box<dyn Any>>,
99    borrow: Rc<Cell<usize>>,
100    _marker: std::marker::PhantomData<T>,
101}
102
103impl<T: 'static> Drop for AppDataRefMut<T> {
104    fn drop(&mut self) {
105        self.borrow.set(self.borrow.get().saturating_sub(1));
106    }
107}
108
109impl<T: 'static> Deref for AppDataRefMut<T> {
110    type Target = T;
111    fn deref(&self) -> &T {
112        self.guard
113            .downcast_ref::<T>()
114            .expect("app data type mismatch")
115    }
116}
117
118impl<T: 'static> DerefMut for AppDataRefMut<T> {
119    fn deref_mut(&mut self) -> &mut T {
120        self.guard
121            .downcast_mut::<T>()
122            .expect("app data type mismatch")
123    }
124}
125
126impl<T: std::fmt::Debug + 'static> std::fmt::Debug for AppDataRefMut<T> {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        (**self).fmt(f)
129    }
130}
131
132impl<T: PartialEq + 'static> PartialEq<T> for AppDataRefMut<T> {
133    fn eq(&self, other: &T) -> bool {
134        (**self) == *other
135    }
136}
137
138impl Lua {
139    /// Insert (or replace) a value of type `T` in this VM's application-data
140    /// store. Mirrors `mlua::Lua::set_app_data`.
141    ///
142    /// # Panics
143    /// Panics if **any** app-data value is currently borrowed.
144    pub fn set_app_data<T: 'static>(&self, data: T) {
145        self.try_set_app_data(data)
146            .expect("cannot mutably borrow app data container");
147    }
148
149    /// Try to insert (or replace) a value of type `T`. Returns the previous
150    /// value, or an error if any app-data value is currently borrowed. Mirrors
151    /// `mlua::Lua::try_set_app_data`.
152    pub fn try_set_app_data<T: 'static>(&self, data: T) -> Result<Option<T>> {
153        let key = unsafe { vm_key(self.state()) };
154        APP_DATA.with(|m| {
155            let mut outer = m.borrow_mut();
156            let store = outer.entry(key).or_default();
157            // Any outstanding borrow blocks mutation of the container (mlua).
158            if store.borrow.get() != 0 {
159                return Err(Error::runtime("cannot mutably borrow app data container"));
160            }
161            let old = store
162                .entries
163                .insert(TypeId::of::<T>(), Rc::new(RefCell::new(Box::new(data))));
164            Ok(old.and_then(|e| {
165                Rc::try_unwrap(e)
166                    .ok()
167                    .and_then(|cell| cell.into_inner().downcast::<T>().ok().map(|b| *b))
168            }))
169        })
170    }
171
172    /// Borrow the application-data value of type `T` immutably, if present.
173    /// Mirrors `mlua::Lua::app_data_ref`.
174    ///
175    /// # Panics
176    /// Panics if the value is currently mutably borrowed.
177    pub fn app_data_ref<T: 'static>(&self) -> Option<AppDataRef<T>> {
178        match self.try_app_data_ref::<T>() {
179            Ok(opt) => opt,
180            Err(_) => panic!("already mutably borrowed"),
181        }
182    }
183
184    /// Try to borrow the application-data value of type `T` immutably. Returns
185    /// `Ok(None)` if absent, `Err` if it is currently mutably borrowed. Mirrors
186    /// `mlua::Lua::try_app_data_ref`.
187    pub fn try_app_data_ref<T: 'static>(&self) -> Result<Option<AppDataRef<T>>> {
188        let key = unsafe { vm_key(self.state()) };
189        let (entry, borrow) = match APP_DATA.with(|m| {
190            let outer = m.borrow();
191            outer.get(&key).and_then(|store| {
192                store
193                    .entries
194                    .get(&TypeId::of::<T>())
195                    .map(|e| (e.clone(), store.borrow.clone()))
196            })
197        }) {
198            Some(pair) => pair,
199            None => return Ok(None),
200        };
201        let guard = entry
202            .try_borrow()
203            .map_err(|_| Error::runtime("app data is currently mutably borrowed"))?;
204        // Extend the borrow lifetime to `'static`; the `_owner` `Rc` we keep
205        // alongside keeps the `RefCell` alive for as long as the guard lives.
206        let guard: std::cell::Ref<'static, Box<dyn Any>> = unsafe { std::mem::transmute(guard) };
207        borrow.set(borrow.get() + 1);
208        Ok(Some(AppDataRef {
209            _owner: entry,
210            guard,
211            borrow,
212            _marker: std::marker::PhantomData,
213        }))
214    }
215
216    /// Borrow the application-data value of type `T` mutably, if present.
217    /// Mirrors `mlua::Lua::app_data_mut`.
218    ///
219    /// # Panics
220    /// Panics if the value is currently borrowed (immutably or mutably).
221    pub fn app_data_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
222        match self.try_app_data_mut::<T>() {
223            Ok(opt) => opt,
224            Err(_) => panic!("already borrowed"),
225        }
226    }
227
228    /// Try to borrow the application-data value of type `T` mutably. Returns
229    /// `Ok(None)` if absent, `Err` if it is currently borrowed. Mirrors
230    /// `mlua::Lua::try_app_data_mut`.
231    pub fn try_app_data_mut<T: 'static>(&self) -> Result<Option<AppDataRefMut<T>>> {
232        let key = unsafe { vm_key(self.state()) };
233        let (entry, borrow) = match APP_DATA.with(|m| {
234            let outer = m.borrow();
235            outer.get(&key).and_then(|store| {
236                store
237                    .entries
238                    .get(&TypeId::of::<T>())
239                    .map(|e| (e.clone(), store.borrow.clone()))
240            })
241        }) {
242            Some(pair) => pair,
243            None => return Ok(None),
244        };
245        let guard = entry
246            .try_borrow_mut()
247            .map_err(|_| Error::runtime("app data is currently borrowed"))?;
248        let guard: std::cell::RefMut<'static, Box<dyn Any>> = unsafe { std::mem::transmute(guard) };
249        borrow.set(borrow.get() + 1);
250        Ok(Some(AppDataRefMut {
251            _owner: entry,
252            guard,
253            borrow,
254            _marker: std::marker::PhantomData,
255        }))
256    }
257
258    /// Remove and return the application-data value of type `T`, if present.
259    /// Mirrors `mlua::Lua::remove_app_data`.
260    ///
261    /// # Panics
262    /// Panics if **any** app-data value is currently borrowed.
263    pub fn remove_app_data<T: 'static>(&self) -> Option<T> {
264        let key = unsafe { vm_key(self.state()) };
265        APP_DATA.with(|m| {
266            let mut outer = m.borrow_mut();
267            let store = outer.get_mut(&key)?;
268            if store.borrow.get() != 0 {
269                panic!("cannot mutably borrow app data container");
270            }
271            let entry = store.entries.remove(&TypeId::of::<T>())?;
272            Rc::try_unwrap(entry)
273                .ok()
274                .and_then(|cell| cell.into_inner().downcast::<T>().ok().map(|b| *b))
275        })
276    }
277}
278
279/// Drop this VM's entire application-data store. Called from `LuaInner::drop`.
280pub(crate) fn clear_app_data(state: *mut lua_State) {
281    let key = unsafe { vm_key(state) };
282    APP_DATA.with(|m| {
283        m.borrow_mut().remove(&key);
284    });
285}