context.rs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. use crate::{nodebuilder::IntoDomTree, prelude::*};
  2. use crate::{nodebuilder::LazyNodes, nodes::VNode};
  3. use bumpalo::Bump;
  4. use hooks::Hook;
  5. use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::atomic::AtomicUsize};
  6. /// Components in Dioxus use the "Context" object to interact with their lifecycle.
  7. /// This lets components schedule updates, integrate hooks, and expose their context via the context api.
  8. ///
  9. /// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
  10. ///
  11. /// ```ignore
  12. /// #[derive(Properties)]
  13. /// struct Props {
  14. /// name: String
  15. ///
  16. /// }
  17. ///
  18. /// fn example(ctx: Context, props: &Props -> VNode {
  19. /// html! {
  20. /// <div> "Hello, {ctx.props.name}" </div>
  21. /// }
  22. /// }
  23. /// ```
  24. // todo: force lifetime of source into T as a valid lifetime too
  25. // it's definitely possible, just needs some more messing around
  26. pub struct Context<'src> {
  27. pub idx: RefCell<usize>,
  28. pub scope: ScopeIdx,
  29. // Borrowed from scope
  30. // pub(crate) arena: &'src typed_arena::Arena<Hook>,
  31. pub(crate) hooks: &'src RefCell<Vec<Hook>>,
  32. // pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
  33. pub(crate) bump: &'src Bump,
  34. pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
  35. // holder for the src lifetime
  36. // todo @jon remove this
  37. pub _p: std::marker::PhantomData<&'src ()>,
  38. }
  39. impl<'a> Context<'a> {
  40. /// Access the children elements passed into the component
  41. pub fn children(&self) -> Vec<VNode> {
  42. todo!("Children API not yet implemented for component Context")
  43. }
  44. pub fn callback(&self, _f: impl Fn(()) + 'a) {}
  45. // call this closure after the component has been committed to the editlist
  46. // this provides the founation of "use_effect"
  47. fn post_update() {}
  48. /// Create a subscription that schedules a future render for the reference component
  49. pub fn schedule_update(&self) -> impl Fn() -> () {
  50. || {}
  51. }
  52. /// Create a suspended component from a future.
  53. ///
  54. /// When the future completes, the component will be renderered
  55. pub fn suspend<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
  56. &self,
  57. _fut: impl Future<Output = LazyNodes<'a, F>>,
  58. ) -> VNode<'a> {
  59. todo!()
  60. }
  61. }
  62. // NodeCtx is used to build VNodes in the component's memory space.
  63. // This struct adds metadata to the final DomTree about listeners, attributes, and children
  64. #[derive(Debug, Clone)]
  65. pub struct NodeCtx<'a> {
  66. pub bump: &'a Bump,
  67. pub listeners: &'a RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
  68. pub idx: RefCell<usize>,
  69. pub scope: ScopeIdx,
  70. }
  71. impl<'a> Context<'a> {
  72. /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
  73. ///
  74. /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
  75. ///
  76. /// ## Example
  77. ///
  78. /// ```ignore
  79. /// fn Component(ctx: Context<Props>) -> VNode {
  80. /// // Lazy assemble the VNode tree
  81. /// let lazy_tree = html! {<div> "Hello World" </div>};
  82. ///
  83. /// // Actually build the tree and allocate it
  84. /// ctx.render(lazy_tree)
  85. /// }
  86. ///```
  87. pub fn render<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
  88. &self,
  89. lazy_nodes: LazyNodes<'a, F>,
  90. ) -> DomTree {
  91. let ctx = NodeCtx {
  92. bump: self.bump,
  93. scope: self.scope,
  94. idx: 0.into(),
  95. listeners: self.listeners,
  96. };
  97. let safe_nodes = lazy_nodes.into_vnode(&ctx);
  98. let root: VNode<'static> = unsafe { std::mem::transmute(safe_nodes) };
  99. DomTree { root }
  100. }
  101. }
  102. /// This module provides internal state management functionality for Dioxus components
  103. pub mod hooks {
  104. use std::any::Any;
  105. use super::*;
  106. #[derive(Debug)]
  107. pub struct Hook(pub Pin<Box<dyn std::any::Any>>);
  108. impl Hook {
  109. pub fn new<T: 'static>(state: T) -> Self {
  110. Self(Box::pin(state))
  111. }
  112. }
  113. impl<'a> Context<'a> {
  114. /// TODO: @jon, rework this so we dont have to use unsafe to make hooks and then return them
  115. /// use_hook provides a way to store data between renders for functional components.
  116. /// todo @jon: ensure the hook arena is stable with pin or is stable by default
  117. pub fn use_hook<'scope, InternalHookState: 'static, Output: 'a>(
  118. &'scope self,
  119. // The closure that builds the hook state
  120. initializer: impl FnOnce() -> InternalHookState,
  121. // The closure that takes the hookstate and returns some value
  122. runner: impl FnOnce(&'a mut InternalHookState) -> Output,
  123. // The closure that cleans up whatever mess is left when the component gets torn down
  124. // TODO: add this to the "clean up" group for when the component is dropped
  125. _cleanup: impl FnOnce(InternalHookState),
  126. ) -> Output {
  127. let idx = *self.idx.borrow();
  128. // Mutate hook list if necessary
  129. let mut hooks = self.hooks.borrow_mut();
  130. // Initialize the hook by allocating it in the typed arena.
  131. // We get a reference from the arena which is owned by the component scope
  132. // This is valid because "Context" is only valid while the scope is borrowed
  133. if idx >= hooks.len() {
  134. let new_state = initializer();
  135. hooks.push(Hook::new(new_state));
  136. }
  137. *self.idx.borrow_mut() = 1;
  138. let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
  139. let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
  140. let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
  141. // we extend the lifetime from borrowed in this scope to borrowed from self.
  142. // This is okay because the hook is pinned
  143. runner(unsafe { &mut *(internal_state as *mut _) })
  144. /*
  145. ** UNSAFETY ALERT **
  146. Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
  147. However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
  148. into the arena. During the first call of the function, we need to add the mutable reference given to us by
  149. the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
  150. when the component itself is deallocated.
  151. This is okay because:
  152. - The lifetime of the component arena is tied to the lifetime of these raw hooks
  153. - Usage of the raw hooks is tied behind the Vec refcell
  154. - Output is static, meaning it can't take a reference to the data
  155. - We don't expose the raw hook pointer outside of the scope of use_hook
  156. - The reference is tied to context, meaning it can only be used while ctx is around to free it
  157. */
  158. // let raw_hook: &'scope mut _ = unsafe { &mut *raw_hook };
  159. // let p = raw_hook.0.downcast_mut::<InternalHookState>();
  160. // let r = p.unwrap();
  161. // let v = unsafe { Pin::get_unchecked_mut(raw_hook) };
  162. // // let carreied_ref: &'scope mut dyn Any = unsafe { &mut *v };
  163. // let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
  164. // let real_internal = unsafe { internal_state as *mut _ };
  165. // runner(unsafe { &mut *real_internal })
  166. // runner(internal_state)
  167. }
  168. }
  169. }
  170. /// Context API
  171. ///
  172. /// The context API provides a mechanism for components to borrow state from other components higher in the tree.
  173. /// By combining the Context API and the Subscription API, we can craft ergonomic global state management systems.
  174. ///
  175. /// This API is inherently dangerous because we could easily cause UB by allowing &T and &mut T to exist at the same time.
  176. /// To prevent this, we expose the RemoteState<T> and RemoteLock<T> types which act as a form of reverse borrowing.
  177. /// This is very similar to RwLock, except that RemoteState is copy-able. Unlike RwLock, derefing RemoteState can
  178. /// cause panics if the pointer is null. In essence, we sacrifice the panic protection for ergonomics, but arrive at
  179. /// a similar end result.
  180. ///
  181. /// Instead of placing the onus on the receiver of the data to use it properly, we wrap the source object in a
  182. /// "shield" where gaining &mut access can only be done if no active StateGuards are open. This would fail and indicate
  183. /// a failure of implementation.
  184. pub mod context_api {
  185. use std::ops::Deref;
  186. pub struct RemoteState<T> {
  187. inner: *const T,
  188. }
  189. impl<T> Copy for RemoteState<T> {}
  190. impl<T> Clone for RemoteState<T> {
  191. fn clone(&self) -> Self {
  192. Self { inner: self.inner }
  193. }
  194. }
  195. static DEREF_ERR_MSG: &'static str = r#"""
  196. [ERROR]
  197. This state management implementation is faulty. Report an issue on whatever implementation is using this.
  198. Context should *never* be dangling!. If a Context is torn down, so should anything that references it.
  199. """#;
  200. impl<T> Deref for RemoteState<T> {
  201. type Target = T;
  202. fn deref(&self) -> &Self::Target {
  203. // todo!
  204. // Try to borrow the underlying context manager, register this borrow with the manager as a "weak" subscriber.
  205. // This will prevent the panic and ensure the pointer still exists.
  206. // For now, just get an immutable reference to the underlying context data.
  207. //
  208. // It's important to note that ContextGuard is not a public API, and can only be made from UseContext.
  209. // This guard should only be used in components, and never stored in hooks
  210. unsafe {
  211. match self.inner.as_ref() {
  212. Some(ptr) => ptr,
  213. None => panic!(DEREF_ERR_MSG),
  214. }
  215. }
  216. }
  217. }
  218. impl<'a> super::Context<'a> {
  219. // impl<'a, P> super::Context<'a, P> {
  220. pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
  221. todo!()
  222. }
  223. pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
  224. }
  225. /// # SAFETY ALERT
  226. ///
  227. /// The underlying context mechanism relies on mutating &mut T while &T is held by components in the tree.
  228. /// By definition, this is UB. Therefore, implementing use_context should be done with upmost care to invalidate and
  229. /// prevent any code where &T is still being held after &mut T has been taken and T has been mutated.
  230. ///
  231. /// While mutating &mut T while &T is captured by listeners, we can do any of:
  232. /// 1) Prevent those listeners from being called and avoid "producing" UB values
  233. /// 2) Delete instances of closures where &T is captured before &mut T is taken
  234. /// 3) Make clones of T to preserve the original &T.
  235. /// 4) Disable any &T remotely (like RwLock, RefCell, etc)
  236. ///
  237. /// To guarantee safe usage of state management solutions, we provide Dioxus-Reducer and Dioxus-Dataflow built on the
  238. /// SafeContext API. This should provide as an example of how to implement context safely for 3rd party state management.
  239. ///
  240. /// It's important to recognize that while safety is a top concern for Dioxus, ergonomics do take prescendence.
  241. /// Contrasting with the JS ecosystem, Rust is faster, but actually "less safe". JS is, by default, a "safe" language.
  242. /// However, it does not protect you against data races: the primary concern for 3rd party implementers of Context.
  243. ///
  244. /// We guarantee that any &T will remain consistent throughout the life of the Virtual Dom and that
  245. /// &T is owned by components owned by the VirtualDom. Therefore, it is impossible for &T to:
  246. /// - be dangling or unaligned
  247. /// - produce an invalid value
  248. /// - produce uninitialized memory
  249. ///
  250. /// The only UB that is left to the implementer to prevent are Data Races.
  251. ///
  252. /// Here's a strategy that is UB:
  253. /// 1. &T is handed out via use_context
  254. /// 2. an event is reduced against the state
  255. /// 3. An &mut T is taken
  256. /// 4. &mut T is mutated.
  257. ///
  258. /// Now, any closures that caputed &T are subject to a data race where they might have skipped checks and UB
  259. /// *will* affect the program.
  260. ///
  261. /// Here's a strategy that's not UB (implemented by SafeContext):
  262. /// 1. ContextGuard<T> is handed out via use_context.
  263. /// 2. An event is reduced against the state.
  264. /// 3. The state is cloned.
  265. /// 4. All subfield selectors are evaluated and then diffed with the original.
  266. /// 5. Fields that have changed have their ContextGuard poisoned, revoking their ability to take &T.a.
  267. /// 6. The affected fields of Context are mutated.
  268. /// 7. Scopes with poisoned guards are regenerated so they can take &T.a again, calling their lifecycle.
  269. ///
  270. /// In essence, we've built a "partial borrowing" mechanism for Context objects.
  271. ///
  272. /// =================
  273. /// nb
  274. /// =================
  275. /// If you want to build a state management API directly and deal with all the unsafe and UB, we provide
  276. /// `use_context_unchecked` with all the stability with *no* guarantess of Data Race protection. You're on
  277. /// your own to not affect user applications.
  278. ///
  279. /// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
  280. /// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
  281. fn blah() {}
  282. }