Skip to main content

wry_bindgen_core/
runtime.rs

1//! The `Runtime` handle and its accessor.
2//!
3//! `Runtime` exposes borrow-safe runtime operations as methods. Operations that
4//! must run code with the runtime released — calling a JS function
5//! ([`JsFunction`](crate::JsFunction)), borrowing a stored object
6//! ([`Runtime::object`]), or dropping a value — are deliberately not methods here.
7//!
8//! The handle wraps the concrete [`wry_bindgen_runtime::wire::Runtime`] but only
9//! re-exposes the borrow-safe, semantic subset of its operations.
10
11use alloc::boxed::Box;
12use core::ops::{Deref, DerefMut};
13
14use wry_bindgen_runtime::wire::Runtime as RuntimeState;
15use wry_bindgen_runtime::wire::{DecodedData, EncodedData, JsRef, ObjectHandle};
16
17use crate::BatchableResult;
18
19/// An active borrow of an object from the runtime.
20///
21/// The object is reinserted when this value is dropped unless a drop for the
22/// handle was requested during the checkout.
23struct CheckedOutObject<T: 'static> {
24    handle: ObjectHandle,
25    value: Option<T>,
26}
27
28impl<T: 'static> CheckedOutObject<T> {
29    /// Check the object out using the already-borrowed runtime. `object()` is
30    /// itself called from inside a [`with_runtime`] closure, so this must not
31    /// re-enter `with_runtime` (that would be a double borrow).
32    fn checkout(rt: &mut Runtime, handle: ObjectHandle) -> Self {
33        let value = rt.take_object::<T>(handle).expect("invalid handle");
34        Self {
35            handle,
36            value: Some(value),
37        }
38    }
39
40    fn get(&self) -> &T {
41        self.value.as_ref().expect("checked-out object missing")
42    }
43
44    fn get_mut(&mut self) -> &mut T {
45        self.value.as_mut().expect("checked-out object missing")
46    }
47}
48
49impl<T> Deref for CheckedOutObject<T> {
50    type Target = T;
51
52    fn deref(&self) -> &Self::Target {
53        self.get()
54    }
55}
56
57impl<T> DerefMut for CheckedOutObject<T> {
58    fn deref_mut(&mut self) -> &mut Self::Target {
59        self.get_mut()
60    }
61}
62
63impl<T: 'static> Drop for CheckedOutObject<T> {
64    fn drop(&mut self) {
65        if let Some(value) = self.value.take() {
66            with_runtime(|rt| rt.reinsert_object(self.handle, value));
67        }
68    }
69}
70
71/// A handle to the active runtime, scoped to a [`with_runtime`] call.
72pub struct Runtime<'a> {
73    backend: &'a mut RuntimeState,
74}
75
76impl Runtime<'_> {
77    /// Store a Rust value, returning an opaque handle to it.
78    pub fn insert_object<T: 'static>(&mut self, obj: T) -> ObjectHandle {
79        self.backend.insert_object_box(Box::new(obj))
80    }
81
82    fn take_object<T: 'static>(&mut self, handle: ObjectHandle) -> Option<T> {
83        self.backend
84            .take_object_box(handle)
85            .map(|obj| *obj.downcast::<T>().expect("object type mismatch"))
86    }
87
88    /// Temporarily borrow a stored object by handle.
89    pub fn object<T: 'static>(
90        &mut self,
91        handle: ObjectHandle,
92    ) -> impl DerefMut<Target = T> + use<T> {
93        CheckedOutObject::checkout(self, handle)
94    }
95
96    /// Permanently remove a stored value by handle, freeing the handle.
97    pub fn remove_object<T: 'static>(&mut self, handle: ObjectHandle) -> Option<T> {
98        self.backend
99            .remove_object_untyped(handle)
100            .map(|obj| *obj.downcast::<T>().expect("object type mismatch"))
101    }
102
103    fn reinsert_object<T: 'static>(&mut self, handle: ObjectHandle, obj: T) {
104        self.backend.reinsert_object_box(handle, Box::new(obj));
105    }
106
107    /// Reserve the next return-value placeholder JS reference.
108    ///
109    /// Batched calls reserve the heap slot here so the typed result can be
110    /// produced without a round-trip; JS fills the slot on the next flush.
111    pub(crate) fn next_placeholder_ref(&mut self) -> JsRef {
112        self.backend.next_placeholder_ref()
113    }
114}
115
116/// Run `f` with the active runtime.
117///
118/// Panics if no runtime is installed, or if one is already borrowed (e.g. a
119/// re-entrant call from inside another `with_runtime`).
120pub fn with_runtime<R>(f: impl FnOnce(&mut Runtime) -> R) -> R {
121    wry_bindgen_runtime::wire::with_runtime(|backend| f(&mut Runtime { backend }))
122}
123
124pub(crate) fn with_backend<R>(f: impl FnOnce(&mut RuntimeState) -> R) -> R {
125    wry_bindgen_runtime::wire::with_runtime(f)
126}
127
128/// Call a JS function synchronously and decode its typed result.
129///
130/// The function id, placeholder reservation, and flush are all internal to the
131/// runtime; this wrapper supplies the typed encode/decode for `R`.
132pub(crate) fn run_js_sync<R: BatchableResult + 'static>(
133    fn_id: u32,
134    add_args: impl FnOnce(&mut EncodedData),
135) -> R {
136    wry_bindgen_runtime::wire::run_js_sync(
137        fn_id,
138        add_args,
139        |backend| R::try_placeholder(&mut Runtime { backend }),
140        |mut data: DecodedData<'_>| R::decode(&mut data).expect("Failed to decode return value"),
141    )
142}