Skip to main content

yarte_wasm_app/
lib.rs

1//! # Yarte wasm application
2//! A simple reactor pattern
3//!
4//! Intended to be used as a singleton and static with single state
5//!
6//! Only 101% rust safe in nightly
7//!
8//! ## Architecture
9//! ### Cycle
10//! The cycle of App methods is:
11//! - `init`:
12//!     - `__hydrate(&mut self, _addr: A<Self>)`
13//! - `on message`:
14//!     - enqueue message
15//!     - is ready? -> `update`
16//! - `update`
17//!     - pop message? -> `__dispatch(&mut self, _msg: Self::Message, _addr: A<Self>)`
18//!     - is queue empty?  -> `__render(&mut self, _addr: A<Self>)`
19//!     - is queue not empty? -> `update`
20//!
21//! ### Virtual DOM and differences in tree
22//! The virtual DOM model and the difference calculation in the node tree
23//! differs from all previous implementations in many ways.
24//! The main aspect that is not found in any other implementation
25//! is that it accumulates the changes in a tree of differences
26//! equivalent space dimension is less or equal than
27//! html node tree equivalent space dimension.
28//!
29//! It's compiled with static linking of the modules that allows to
30//! detect local fixed points at compile time and change the base of the
31//! difference linear map. That is, instead of making the difference in the html nodes,
32//! it make the difference on a tree of differences write at process line.
33//!
34//! With what allows a reduction of the dimension of the domain and strong optimizations in parallel.
35//!
36//! ### Unsafe code and controversies
37//! #### Why no RC?
38//! Because you don't need it because it is thinking to be implemented as singleton and static.
39//!
40//! ### Whe no RefCell?
41//! Because you don't need it because all uniques (mutable) references are made in atomic functions,
42//! `run!` is designed for assure **unique** owner of **all** `App` is `Addr` and its unique safe method
43//! is `send`
44//!
45//! #### Why no backpressure?
46//! Because you don't need it because you don't need a runtime to poll wasm futures.
47//! Backpressure can be implemented for future it is needed and carry static reference to the
48//! Address of the App.
49//!
50//! #### Why doubly-linked list?
51//! Is simpler than grow array implementation and it will never be a bottleneck in a browser.
52//! But in the future it can be implemented.
53//!
54#![no_std]
55#![cfg_attr(nightly, feature(core_intrinsics, negative_impls))]
56
57extern crate alloc;
58
59use alloc::boxed::Box;
60use core::cell::{Cell, UnsafeCell};
61use core::default::Default;
62#[cfg(nightly)]
63use core::marker::{Send, Sync};
64
65#[cfg(debug_assertions)]
66use alloc::alloc::{dealloc, Layout};
67#[cfg(debug_assertions)]
68use core::ptr;
69
70mod queue;
71
72use self::queue::Queue;
73
74/// App are object which encapsulate state and behavior
75///
76/// App communicate exclusively by directional exchanging messages.
77///
78/// It is recommended not to implement out of WASM Single Page Application context.
79// TODO: derive
80pub trait App: Default + 'static {
81    type BlackBox;
82    type Message: 'static;
83    /// Private: empty for overridden in derive
84    #[doc(hidden)]
85    #[inline]
86    fn __render(&mut self, _addr: A<Self>) {}
87
88    /// Private: empty for overridden in derive
89    #[doc(hidden)]
90    #[inline]
91    fn __hydrate(&mut self, _addr: A<Self>) {}
92
93    /// Private: empty for overridden in derive
94    #[doc(hidden)]
95    #[inline]
96    fn __dispatch(&mut self, _msg: Self::Message, _addr: A<Self>) {}
97}
98
99/// The address of App
100pub struct Addr<A: App>(pub(crate) Context<A>);
101
102#[cfg(not(debug_assertions))]
103impl<A: App> Drop for Addr<A> {
104    fn drop(&mut self) {
105        panic!("drop app")
106    }
107}
108
109/// Macro to create a `A<App: App>` reference to a statically allocated `App`.
110///
111/// This macro returns a value with type `&'static Addr<$ty>`.
112///
113/// # Panics
114/// Have one type instance
115/// Only construct to target arch `wasm32`
116///
117/// ```ignore
118/// #[derive(App)]
119/// #[template(path = "index")
120/// #[msg(enum Msg { Inc, Reset })]
121/// struct MyApp {
122///     count: usize,
123///     bb: <Self as App>::BlackBox,
124/// }
125///
126/// fn inc(app: &mut MyApp, _addr: &Addr<MyApp>) {
127///     set_count!(app, app.count + 1);
128/// }
129///
130/// fn reset(app: &mut MyApp, _addr: &Addr<MyApp>) {
131///     if app.count != 0 {
132///         set_count!(app, 0);
133///     }
134/// }
135///
136/// let addr = run!(MyApp);
137/// addr.send(Msg::Reset);
138/// ```
139#[macro_export]
140macro_rules! run {
141    ($ty:ty) => {
142        unsafe { $crate::A::run(<$ty as core::default::Default>::default()) }
143    };
144}
145
146/// DeLorean for your app. Easy and safe traveling to the future in your thread and the nightly
147///
148/// No Send and No Sync wrapper static reference
149pub struct A<I: App>(&'static Addr<I>);
150pub use self::A as DeLorean;
151
152#[cfg(nightly)]
153impl<I: App> !Send for A<I> {}
154#[cfg(nightly)]
155impl<I: App> !Sync for A<I> {}
156
157impl<I: App> Clone for A<I> {
158    #[inline(always)]
159    fn clone(&self) -> Self {
160        A(self.0)
161    }
162}
163
164impl<I: App> Copy for A<I> {}
165
166impl<I: App> A<I> {
167    /// Make new Address for App and run it
168    ///
169    /// # Panics
170    /// Only run it in target arch `wasm32`
171    ///
172    /// # Safety
173    /// Can broke needed atomicity of unique references and queue pop
174    pub unsafe fn run(a: I) -> A<I> {
175        let addr = A(Addr::new(a));
176        // SAFETY: only run one time
177        addr.hydrate();
178        addr
179    }
180
181    /// Dealloc Address
182    ///
183    /// Use for testing
184    ///
185    /// # Safety
186    /// Broke `'static` lifetime and all copies are nothing,
187    /// World could explode
188    #[cfg(debug_assertions)]
189    pub unsafe fn dealloc(self) {
190        self.0.dealloc();
191    }
192
193    /// Sends a message
194    ///
195    /// The message is always queued
196    pub fn send(self, msg: I::Message) {
197        self.ctx().push(msg);
198        self.update();
199    }
200
201    /// Hydrate app
202    /// Link events and save closures
203    ///
204    /// # Safety
205    /// Produce **unexpected behaviour** if launched more than one time
206    #[inline]
207    unsafe fn hydrate(self) {
208        let ctx = self.ctx();
209        debug_assert!(!ctx.is_ready());
210        ctx.app().__hydrate(self);
211        ctx.ready(true);
212    }
213
214    #[inline]
215    fn update(self) {
216        let ctx = self.ctx();
217        if ctx.is_ready() {
218            ctx.ready(false);
219            // SAFETY: UB is checked by ready Cell
220            unsafe {
221                while let Some(msg) = ctx.pop() {
222                    ctx.app().__dispatch(msg, self);
223                    while let Some(msg) = ctx.pop() {
224                        ctx.app().__dispatch(msg, self);
225                    }
226                    ctx.app().__render(self);
227                }
228            }
229            ctx.ready(true);
230        }
231    }
232
233    #[inline]
234    fn ctx(&self) -> &Context<I> {
235        &(self.0).0
236    }
237}
238
239/// Constructor and destructor
240impl<I: App> Addr<I> {
241    /// Make new Address for App
242    ///
243    /// Use at `run!` macro and for testing
244    ///
245    /// # Panics
246    /// Only construct in target arch `wasm32`
247    #[inline]
248    fn new(a: I) -> &'static Addr<I> {
249        if cfg!(not(target_arch = "wasm32")) {
250            panic!("Only construct in 'wasm32'");
251        }
252        Box::leak(Box::new(Addr(Context::new(a))))
253    }
254
255    /// Dealloc Address
256    ///
257    /// Use for testing
258    ///
259    /// # Safety
260    /// Broke `'static` lifetime
261    #[cfg(debug_assertions)]
262    pub(crate) unsafe fn dealloc(&'static self) {
263        let p = stc_to_ptr(self);
264        ptr::drop_in_place(p);
265        dealloc(p as *mut u8, Layout::new::<Addr<I>>());
266    }
267}
268
269/// Encapsulate inner context of the App
270pub struct Context<A: App> {
271    app: UnsafeCell<A>,
272    q: Queue<A::Message>,
273    ready: Cell<bool>,
274}
275
276impl<A: App> Context<A> {
277    pub(crate) fn new(app: A) -> Self {
278        Self {
279            app: UnsafeCell::new(app),
280            q: Queue::new(),
281            ready: Cell::new(false),
282        }
283    }
284
285    /// Get unsafe mutable reference of A
286    ///
287    /// # Safety
288    /// Unchecked null pointer and broke mutability
289    #[inline]
290    #[allow(clippy::mut_from_ref)]
291    pub(crate) unsafe fn app(&self) -> &mut A {
292        &mut *self.app.get()
293    }
294
295    /// Set ready
296    #[inline]
297    pub(crate) fn ready(&self, r: bool) {
298        self.ready.replace(r);
299    }
300
301    /// Is ready
302    #[inline]
303    pub(crate) fn is_ready(&self) -> bool {
304        self.ready.get()
305    }
306
307    /// Enqueue message
308    #[inline]
309    pub(crate) fn push(&self, msg: A::Message) {
310        self.q.push(msg);
311    }
312
313    /// Pop message
314    ///
315    /// # Safety
316    /// Only call in a atomic function
317    #[inline]
318    pub(crate) unsafe fn pop(&self) -> Option<A::Message> {
319        self.q.pop()
320    }
321}
322
323/// Static ref to mutable ptr
324///
325/// # Safety
326/// Broke `'static` lifetime and mutability
327#[cfg(debug_assertions)]
328const unsafe fn stc_to_ptr<T>(t: &'static T) -> *mut T {
329    t as *const T as *mut T
330}
331
332/// unchecked unwrap
333///
334/// # Safety
335/// `None` produce UB
336#[inline]
337#[cfg(not(nightly))]
338unsafe fn unwrap<T>(o: Option<T>) -> T {
339    o.unwrap()
340}
341
342/// unchecked unwrap
343///
344/// # Safety
345/// `None` produce UB
346#[inline]
347#[cfg(nightly)]
348unsafe fn unwrap<T>(o: Option<T>) -> T {
349    o.unwrap_or_else(|| core::intrinsics::unreachable())
350}