spore_vm/val/
custom.rs

1use std::{
2    any::Any,
3    marker::PhantomData,
4    ops::{Deref, DerefMut},
5    sync::{RwLock, RwLockReadGuard, RwLockWriteGuard, TryLockError},
6};
7
8#[allow(unused_imports)]
9use log::*;
10use thiserror::Error;
11
12#[derive(Error, Debug, PartialEq)]
13pub enum CustomValError {
14    #[error("Lock is poisoned")]
15    LockPoisoned,
16    #[error("Lock is not available")]
17    LockNotAvailable,
18    #[error("expected type {expected} but found type {actual}")]
19    WrongType {
20        expected: &'static str,
21        actual: &'static str,
22    },
23}
24
25impl<T> From<TryLockError<T>> for CustomValError {
26    fn from(value: TryLockError<T>) -> Self {
27        match value {
28            // Untested OK: It hard to recreate poisoning a lock while also not invalidating the
29            // entire VM. Not terribly bad not to test as the scenario will rarely matter.
30            TryLockError::Poisoned(_) => CustomValError::LockPoisoned,
31            TryLockError::WouldBlock => CustomValError::LockNotAvailable,
32        }
33    }
34}
35
36/// Contains a custom value.
37#[derive(Debug)]
38pub struct CustomVal(pub(crate) RwLock<Box<dyn CustomTypeSealed>>);
39
40impl CustomVal {
41    /// Create a new `CustomVal` from any type that implements `CustomType`.
42    pub fn new(val: impl CustomType) -> CustomVal {
43        CustomVal(RwLock::new(Box::new(val)))
44    }
45
46    /// Get the underlying value of [Self] if it is of type `T` or else return `None`.
47    ///
48    /// # Panic
49    /// Panics if the value if the value is under a [Self::get_mut].
50    pub fn get<T>(&self) -> Result<CustomValRef<'_, T>, CustomValError>
51    where
52        T: CustomType,
53    {
54        let guard = self.0.try_read()?;
55        let want_type_id = std::any::TypeId::of::<T>();
56        let have_type_id = guard.as_any().type_id();
57
58        if want_type_id == have_type_id {
59            Ok(CustomValRef {
60                guard,
61                _type: PhantomData,
62            })
63        } else {
64            Err(CustomValError::WrongType {
65                expected: std::any::type_name::<T>(),
66                actual: guard.name(),
67            })
68        }
69    }
70
71    /// Get the underlying value of [Self] if it is of type `T` or else return `None`.
72    ///
73    /// # Panic
74    /// Panics if the value if the value is under a [Self::get] or [Self::get_mut].
75    pub fn get_mut<T>(&self) -> Result<CustomValMut<T>, CustomValError>
76    where
77        T: CustomType,
78    {
79        let guard = self.0.try_write()?;
80        let want_type_id = std::any::TypeId::of::<T>();
81        let have_type_id = guard.as_any().type_id();
82        if want_type_id == have_type_id {
83            Ok(CustomValMut {
84                guard,
85                _type: PhantomData,
86            })
87        } else {
88            Err(CustomValError::WrongType {
89                expected: std::any::type_name::<T>(),
90                actual: guard.name(),
91            })
92        }
93    }
94}
95
96impl std::fmt::Display for CustomVal {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        match self.0.try_read() {
99            Err(_) => write!(f, "<custom-value-read-locked>"),
100            Ok(obj) => obj.deref().fmt(f),
101        }
102    }
103}
104
105#[derive(Debug)]
106pub struct CustomValRef<'a, T> {
107    guard: RwLockReadGuard<'a, Box<dyn CustomTypeSealed>>,
108    _type: PhantomData<T>,
109}
110
111impl<'a, T: 'static> Deref for CustomValRef<'a, T> {
112    type Target = T;
113
114    fn deref(&self) -> &Self::Target {
115        self.guard.as_ref().as_any().downcast_ref::<T>().unwrap()
116    }
117}
118
119#[derive(Debug)]
120pub struct CustomValMut<'a, T> {
121    guard: RwLockWriteGuard<'a, Box<dyn CustomTypeSealed>>,
122    _type: PhantomData<T>,
123}
124
125impl<'a, T: 'static> Deref for CustomValMut<'a, T> {
126    type Target = T;
127
128    fn deref(&self) -> &Self::Target {
129        self.guard.as_ref().as_any().downcast_ref::<T>().unwrap()
130    }
131}
132
133impl<'a, T: 'static> DerefMut for CustomValMut<'a, T> {
134    fn deref_mut(&mut self) -> &mut Self::Target {
135        self.guard
136            .as_mut()
137            .as_any_mut()
138            .downcast_mut::<T>()
139            .unwrap()
140    }
141}
142
143pub(crate) trait CustomTypeSealed:
144    'static + Send + Sync + std::fmt::Display + std::fmt::Debug
145{
146    fn as_any_mut(&mut self) -> &mut dyn Any;
147    fn as_any(&self) -> &dyn Any;
148    fn name(&self) -> &'static str {
149        std::any::type_name_of_val(self)
150    }
151}
152
153/// A trait that defines a value that can be created or referenced within the VM.
154///
155/// ```rust
156/// #[derive(Debug, Default)]
157/// pub struct MyType(i64);
158/// impl spore_vm::val::CustomType for MyType {}
159/// impl std::fmt::Display for MyType {
160///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161///         write!(f, "my number is {}", self.0)
162///     }
163/// }
164/// ```
165pub trait CustomType:
166    'static + Send + Sync + std::fmt::Display + std::fmt::Debug + std::any::Any
167{
168}
169
170impl<T> CustomTypeSealed for T
171where
172    T: CustomType,
173{
174    fn as_any_mut(&mut self) -> &mut dyn Any {
175        self
176    }
177
178    fn as_any(&self) -> &dyn Any {
179        self
180    }
181
182    fn name(&self) -> &'static str {
183        std::any::type_name::<T>()
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::{
191        val::{NativeFunctionContext, UnsafeVal, Val, ValBuilder},
192        Vm, VmResult,
193    };
194
195    #[derive(Debug, PartialEq)]
196    struct MyType {
197        number: i64,
198    }
199
200    impl CustomType for MyType {}
201
202    impl std::fmt::Display for MyType {
203        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204            write!(f, "magic number {}", self.number)
205        }
206    }
207
208    #[test]
209    fn custom_type_can_be_printed() {
210        let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
211        assert_eq!(
212            vm.eval_str("custom-value").unwrap().to_string(),
213            "magic number 42"
214        );
215    }
216
217    #[test]
218    fn custom_type_can_be_accessed() {
219        let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
220        let val = vm.eval_str("custom-value").unwrap();
221        let got = val.try_custom::<MyType>().unwrap();
222        assert_eq!(got.deref(), &MyType { number: 42 });
223    }
224
225    #[test]
226    fn custom_type_can_be_accessed_multiple_times() {
227        let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
228        vm.eval_str("custom-value").unwrap().map(|vm, val1| {
229            let got1 = val1.try_custom::<MyType>().unwrap();
230            let val2 = vm.eval_str("custom-value").unwrap();
231            let got2 = val2.try_custom::<MyType>().unwrap();
232            assert_eq!(got1.deref() as *const MyType, got2.deref() as *const MyType);
233        });
234    }
235
236    #[test]
237    fn custom_type_get_after_get_mut_fails() {
238        let mut vm = std::thread::spawn(|| {
239            let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
240            let val1 = vm.eval_str("custom-value").unwrap();
241            let get1 = val1.try_custom_mut::<MyType>().unwrap();
242            std::mem::forget(get1);
243            std::mem::forget(val1);
244            vm
245        })
246        .join()
247        .unwrap();
248        let val1 = vm.eval_str("custom-value").unwrap();
249        assert_eq!(
250            val1.try_custom::<MyType>().unwrap_err(),
251            CustomValError::LockNotAvailable
252        );
253    }
254
255    #[test]
256    fn display_on_locked_value_does_not_freeze_or_panic() {
257        let mut vm = std::thread::spawn(|| {
258            let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
259            let val1 = vm.eval_str("custom-value").unwrap();
260            let get1 = val1.try_custom_mut::<MyType>().unwrap();
261            std::mem::forget(get1);
262            std::mem::forget(val1);
263            vm
264        })
265        .join()
266        .unwrap();
267        let val1 = vm.eval_str("custom-value").unwrap();
268        assert_eq!(
269            val1.try_custom::<MyType>().unwrap_err(),
270            CustomValError::LockNotAvailable
271        );
272        assert_eq!(val1.to_string(), "<custom-value-read-locked>");
273    }
274
275    #[test]
276    fn custom_type_get_mut_after_read_fails() {
277        let mut vm = std::thread::spawn(|| {
278            let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
279            let val1 = vm.eval_str("custom-value").unwrap();
280            let get1 = val1.try_custom::<MyType>().unwrap();
281            let get2 = val1.try_custom::<MyType>().unwrap();
282            std::mem::forget(get1);
283            std::mem::forget(get2);
284            std::mem::forget(val1);
285            vm
286        })
287        .join()
288        .unwrap();
289        let val1 = vm.eval_str("custom-value").unwrap();
290        assert_eq!(
291            val1.try_custom_mut::<MyType>().unwrap_err(),
292            CustomValError::LockNotAvailable
293        );
294    }
295
296    #[test]
297    fn get_mut_can_mutate_value() {
298        let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
299        {
300            let mutate_val = vm.eval_str("custom-value").unwrap();
301            let mut mutate_val_ref = mutate_val.try_custom_mut::<MyType>().unwrap();
302            *mutate_val_ref = MyType { number: -42 };
303            assert_eq!(mutate_val_ref.deref(), &MyType { number: -42 });
304        }
305
306        let val = vm.eval_str("custom-value").unwrap();
307        assert_eq!(
308            val.try_custom::<MyType>().unwrap().deref(),
309            &MyType { number: -42 }
310        );
311    }
312
313    #[derive(Debug, PartialEq)]
314    struct OtherType {
315        string: &'static str,
316    }
317
318    impl CustomType for OtherType {}
319
320    impl std::fmt::Display for OtherType {
321        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322            write!(f, "magic string {}", self.string)
323        }
324    }
325
326    #[test]
327    fn custom_type_get_and_get_mut_with_wrong_custom_type_fails() {
328        let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
329        let val = vm.eval_str("custom-value").unwrap();
330        assert_eq!(
331            val.try_custom::<OtherType>().unwrap_err(),
332            CustomValError::WrongType {
333                expected: OtherType { string: "" }.name(),
334                actual: MyType { number: 0 }.name(),
335            }
336        );
337        assert_eq!(
338            val.try_custom_mut::<OtherType>().unwrap_err(),
339            CustomValError::WrongType {
340                expected: OtherType { string: "" }.name(),
341                actual: MyType { number: 0 }.name(),
342            }
343        );
344    }
345
346    #[test]
347    fn custom_type_get_and_get_mut_with_wrong_val_type_fails() {
348        let mut vm = Vm::default().with_custom_value("custom-value", MyType { number: 42 });
349        let val = vm.eval_str("42.0").unwrap();
350        assert_eq!(
351            val.try_custom::<OtherType>().unwrap_err(),
352            CustomValError::WrongType {
353                expected: OtherType { string: "" }.name(),
354                actual: UnsafeVal::FLOAT_TYPE_NAME,
355            }
356        );
357        assert_eq!(
358            val.try_custom_mut::<OtherType>().unwrap_err(),
359            CustomValError::WrongType {
360                expected: OtherType { string: "" }.name(),
361                actual: UnsafeVal::FLOAT_TYPE_NAME,
362            }
363        );
364    }
365
366    #[test]
367    fn custom_type_can_be_made_from_native_function() {
368        fn custom_function<'a>(
369            ctx: NativeFunctionContext<'a>,
370            args: &[Val],
371        ) -> VmResult<ValBuilder<'a>> {
372            let number = args[0].try_int().unwrap();
373            let v = MyType { number };
374            Ok(ctx.new_custom(v))
375        }
376        let mut vm = Vm::default().with_native_function("custom-function", custom_function);
377        assert_eq!(
378            vm.eval_str("(custom-function 6)")
379                .unwrap()
380                .try_custom::<MyType>()
381                .unwrap()
382                .deref(),
383            &MyType { number: 6 }
384        );
385    }
386
387    #[test]
388    fn custom_type_has_name() {
389        assert_eq!(
390            MyType { number: 0 }.name(),
391            "spore_vm::val::custom::tests::MyType"
392        );
393        assert_eq!(
394            OtherType { string: "" }.name(),
395            "spore_vm::val::custom::tests::OtherType"
396        );
397    }
398
399    #[test]
400    fn hacks_for_code_coverage() {
401        // Other type is made solely for a few tests.
402        let mut other_type = OtherType { string: "" };
403        other_type.as_any();
404        other_type.as_any_mut();
405        other_type.to_string();
406    }
407}