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}