kobold/
internal.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Kobold internals and types used by the [`view!`](crate::view) macro.
6
7use std::mem::MaybeUninit;
8use std::ops::{Deref, DerefMut};
9use std::pin::Pin;
10
11use wasm_bindgen::prelude::*;
12use web_sys::Node;
13
14use crate::View;
15
16/// Uninitialized stable pointer to `T`.
17///
18/// Used for the initialize-in-place strategy employed by the [`View::build`](View::build) method.
19#[must_use]
20#[repr(transparent)]
21pub struct In<'a, T>(&'a mut MaybeUninit<T>);
22
23/// Initialized stable pointer to `T`.
24///
25/// Used for the initialize-in-place strategy employed by the [`View::build`](View::build) method.
26#[repr(transparent)]
27pub struct Out<'a, T>(&'a mut T);
28
29impl<'a, T> Out<'a, T> {
30    /// Create a new `Out<T>` pointer from a raw pointer to `T`.
31    ///
32    /// # Safety
33    ///
34    /// Caller needs to guarantee that:
35    ///
36    /// 1. `raw` is initialized.
37    /// 2. `raw` is a stable pointer for the entire life of `T`.
38    pub unsafe fn from_raw(raw: *mut T) -> Self {
39        Out(&mut *raw)
40    }
41
42    /// Cast this pointer from `Out<T>` to `Out<U>`.
43    ///
44    /// # Safety
45    ///
46    /// Caller needs to guarantee safety as per usual rules of pointer casting, namely:
47    ///
48    /// 1. `T` and `U` must have the same size.
49    /// 2. `T` and `U` must have the same memory layout.
50    pub unsafe fn cast<U>(self) -> Out<'a, U> {
51        Out(&mut *(self.0 as *mut T as *mut U))
52    }
53}
54
55impl<T> Deref for Out<'_, T> {
56    type Target = T;
57
58    fn deref(&self) -> &T {
59        self.0
60    }
61}
62
63impl<T> DerefMut for Out<'_, T> {
64    fn deref_mut(&mut self) -> &mut T {
65        self.0
66    }
67}
68
69impl<'a, T> In<'a, T> {
70    /// Cast this pointer from `In<T>` to `In<U>`.
71    ///
72    /// # Safety
73    ///
74    /// Caller needs to guarantee safety as per usual rules of pointer casting, namely:
75    ///
76    /// 1. `T` and `U` must have the same size.
77    /// 2. `T` and `U` must have the same memory layout.
78    pub unsafe fn cast<U>(self) -> In<'a, U> {
79        In(&mut *(self.0 as *mut MaybeUninit<T> as *mut MaybeUninit<U>))
80    }
81
82    /// Build this `T` in-place using a raw pointer
83    ///
84    /// # Safety
85    ///
86    /// This method itself is safe since just obtaining a raw pointer by itself is also safe,
87    /// it does however require unsafe code to construct `Out<T>` inside the closure `f`.
88    ///
89    /// ```rust
90    /// use kobold::internal::{In, Out};
91    /// use kobold::init;
92    ///
93    /// struct Foo {
94    ///     int: u32,
95    ///     float: f64,
96    /// }
97    ///
98    /// fn build_in(p: In<Foo>) -> Out<Foo> {
99    ///     let out = p.in_place(|p| unsafe {
100    ///         // Initialize fields of `Foo`
101    ///         init!(p.int = 42);
102    ///         init!(p.float = 3.14);
103    ///
104    ///         // Both fields have been initialized
105    ///         Out::from_raw(p)
106    ///     });
107    ///
108    ///     assert_eq!(out.int, 42);
109    ///     assert_eq!(out.float, 3.14);
110    ///
111    ///     out
112    /// }
113    /// ```
114    pub fn in_place<F>(self, f: F) -> Out<'a, T>
115    where
116        F: FnOnce(*mut T) -> Out<'a, T>,
117    {
118        f(self.0.as_mut_ptr())
119    }
120
121    /// Initialize raw pointer `raw` using a builder closure `f`.
122    ///
123    /// # Safety
124    ///
125    /// Caller must guarantee that `raw` is a stable pointer for the entire life of `T`.
126    ///
127    /// If `raw` has already been initialized this can cause a memory leak, which is safe but undesirable.
128    pub unsafe fn raw<F>(raw: *mut T, f: F) -> Out<'a, T>
129    where
130        F: FnOnce(In<T>) -> Out<T>,
131    {
132        f(In(&mut *(raw as *mut MaybeUninit<T>)))
133    }
134
135    /// Initialize a pinned uninitialized data using a builder closure `f`.
136    ///
137    /// # Safety
138    ///
139    /// This method is safe, it can however leak memory if `pin` has already been initialized.
140    pub fn pinned<F>(pin: Pin<&'a mut MaybeUninit<T>>, f: F) -> Out<'a, T>
141    where
142        F: FnOnce(In<T>) -> Out<T>,
143    {
144        f(In(unsafe { pin.get_unchecked_mut() }))
145    }
146
147    /// Replace previous value of `T` with a new value produced by a builder
148    /// closure `f`. Returns the old value.
149    pub fn replace<F>(at: &mut T, f: F) -> T
150    where
151        F: FnOnce(In<T>) -> Out<T>,
152    {
153        let at = unsafe { &mut *(at as *mut T as *mut MaybeUninit<T>) };
154        let old = unsafe { at.assume_init_read() };
155        let Out(_) = f(In(at));
156
157        old
158    }
159
160    /// Initialize this pointer with some value of `T`.
161    pub fn put(self, val: T) -> Out<'a, T> {
162        Out(self.0.write(val))
163    }
164}
165
166/// Initialize a field of a struct with some expression, see [`In::in_place`](In::in_place).
167#[macro_export]
168macro_rules! init {
169    ($p:ident.$field:ident @ $then:expr) => {
170        $crate::internal::In::raw(std::ptr::addr_of_mut!((*$p).$field), move |$p| $then)
171    };
172    ($p:ident.$field:ident = $val:expr) => {
173        $crate::internal::In::raw(std::ptr::addr_of_mut!((*$p).$field), |$p| $p.put($val))
174    };
175}
176
177/// Wrapper that turns `extern` precompiled JavaScript functions into [`View`](View)s.
178#[repr(transparent)]
179pub struct Precompiled<F>(pub F);
180
181/// Helper function used by the [`view!`](crate::view) macro to provide type hints for
182/// event listeners.
183#[inline]
184pub const fn fn_type_hint<T, F: FnMut(T)>(f: F) -> F {
185    f
186}
187
188impl<F> View for Precompiled<F>
189where
190    F: Fn() -> Node,
191{
192    type Product = Node;
193
194    fn build(self, p: In<Node>) -> Out<Node> {
195        p.put(self.0())
196    }
197
198    fn update(self, _: &mut Node) {}
199}
200
201#[wasm_bindgen]
202extern "C" {
203    #[wasm_bindgen(js_namespace = ["document", "body"], js_name = appendChild)]
204    pub(crate) fn append_body(node: &JsValue);
205    #[wasm_bindgen(js_namespace = document, js_name = createTextNode)]
206    pub(crate) fn text_node(t: &str) -> Node;
207    #[wasm_bindgen(js_namespace = document, js_name = createTextNode)]
208    pub(crate) fn text_node_num(t: f64) -> Node;
209    #[wasm_bindgen(js_namespace = document, js_name = createTextNode)]
210    pub(crate) fn text_node_bool(t: bool) -> Node;
211}
212
213mod hidden {
214    use super::wasm_bindgen;
215
216    #[wasm_bindgen(js_name = "koboldCallback")]
217    pub fn kobold_callback(event: web_sys::Event, closure: *mut (), vcall: usize) {
218        let vcall: fn(web_sys::Event, *mut ()) = unsafe { std::mem::transmute(vcall) };
219
220        vcall(event, closure);
221    }
222}
223
224#[wasm_bindgen(module = "/js/util.js")]
225extern "C" {
226    #[wasm_bindgen(js_name = "appendChild")]
227    pub(crate) fn append_child(parent: &Node, child: &JsValue);
228    #[wasm_bindgen(js_name = "appendBefore")]
229    pub(crate) fn append_before(node: &Node, insert: &JsValue);
230    #[wasm_bindgen(js_name = "removeNode")]
231    pub(crate) fn unmount(node: &JsValue);
232    #[wasm_bindgen(js_name = "replaceNode")]
233    pub(crate) fn replace(old: &JsValue, new: &JsValue);
234
235    #[wasm_bindgen(js_name = "emptyNode")]
236    pub(crate) fn empty_node() -> Node;
237    #[wasm_bindgen(js_name = "fragment")]
238    pub(crate) fn fragment() -> Node;
239    #[wasm_bindgen(js_name = "fragmentDecorate")]
240    pub(crate) fn fragment_decorate(f: &Node) -> Node;
241    #[wasm_bindgen(js_name = "fragmentUnmount")]
242    pub(crate) fn fragment_unmount(f: &Node);
243    #[wasm_bindgen(js_name = "fragmentReplace")]
244    pub(crate) fn fragment_replace(f: &Node, new: &JsValue);
245
246    // `set_text` variants ----------------
247
248    #[wasm_bindgen(js_name = "setTextContent")]
249    pub(crate) fn set_text(el: &Node, t: &str);
250    #[wasm_bindgen(js_name = "setTextContent")]
251    pub(crate) fn set_text_num(el: &Node, t: f64);
252    #[wasm_bindgen(js_name = "setTextContent")]
253    pub(crate) fn set_text_bool(el: &Node, t: bool);
254
255    // `set_attr` variants ----------------
256
257    #[wasm_bindgen(js_name = "setAttribute")]
258    pub(crate) fn set_attr(el: &JsValue, a: &str, v: &str);
259    #[wasm_bindgen(js_name = "setAttribute")]
260    pub(crate) fn set_attr_num(el: &JsValue, a: &str, v: f64);
261    #[wasm_bindgen(js_name = "setAttribute")]
262    pub(crate) fn set_attr_bool(el: &JsValue, a: &str, v: bool);
263
264    // provided attribute setters ----------------
265
266    #[wasm_bindgen(js_name = "setChecked")]
267    pub(crate) fn checked(node: &Node, value: bool);
268    #[wasm_bindgen(js_name = "setClassName")]
269    pub(crate) fn class_name(node: &Node, value: &str);
270    #[wasm_bindgen(js_name = "setHref")]
271    pub(crate) fn href(node: &Node, value: &str);
272    #[wasm_bindgen(js_name = "setStyle")]
273    pub(crate) fn style(node: &Node, value: &str);
274    #[wasm_bindgen(js_name = "setValue")]
275    pub(crate) fn value(node: &Node, value: &str);
276    #[wasm_bindgen(js_name = "setValue")]
277    pub(crate) fn value_num(node: &Node, value: f64);
278
279    // ----------------
280
281    #[wasm_bindgen(js_name = "addClass")]
282    pub(crate) fn add_class(node: &Node, value: &str);
283    #[wasm_bindgen(js_name = "removeClass")]
284    pub(crate) fn remove_class(node: &Node, value: &str);
285    #[wasm_bindgen(js_name = "replaceClass")]
286    pub(crate) fn replace_class(node: &Node, old: &str, value: &str);
287    #[wasm_bindgen(js_name = "toggleClass")]
288    pub(crate) fn toggle_class(node: &Node, class: &str, value: bool);
289
290    // ----------------
291
292    #[wasm_bindgen(js_name = "makeEventHandler")]
293    pub(crate) fn make_event_handler(closure: *mut (), vcall: usize) -> JsValue;
294
295    #[wasm_bindgen(js_name = "checkEventHandler")]
296    pub(crate) fn check_event_handler();
297}
298
299#[cfg(test)]
300mod test {
301    use super::*;
302
303    use std::pin::pin;
304
305    #[test]
306    fn pinned() {
307        let data = pin!(MaybeUninit::uninit());
308        let data = In::pinned(data, |p| p.put(42));
309
310        assert_eq!(*data, 42);
311    }
312
313    // Can't really test view! macros in miri since it needs wasm context.
314    //
315    // This is a small mock of what the macro does however.
316    #[test]
317    fn build_in_place() {
318        fn meaning_builder(p: In<u32>) -> Out<u32> {
319            p.put(42)
320        }
321
322        struct Foo {
323            int: u32,
324            float: f64,
325        }
326
327        let foo = pin!(MaybeUninit::<Foo>::uninit());
328        let foo = In::pinned(foo, |p| {
329            p.in_place(|p| unsafe {
330                init!(p.int @ meaning_builder(p));
331                init!(p.float = 3.14);
332
333                Out::from_raw(p)
334            })
335        });
336
337        assert_eq!(foo.int, 42);
338        assert_eq!(foo.float, 3.14);
339    }
340}