From df4a1b6c7ab6c59a485d0821b1ad23b698cbb69d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 5 Dec 2022 15:30:49 -0800 Subject: [PATCH] chore: finish tweaking context api --- packages/core/src/create.rs | 3 ++- packages/core/src/scheduler/wait.rs | 4 +-- packages/core/src/scopes.rs | 32 +++++++++++------------ packages/core/src/virtual_dom.rs | 10 +++---- packages/fermi/src/hooks/atom_root.rs | 3 ++- packages/hooks/src/usecontext.rs | 15 +++++++---- packages/router/src/components/link.rs | 6 ++--- packages/router/src/components/route.rs | 16 +++++------- packages/router/src/components/router.rs | 11 ++++---- packages/router/src/hooks/use_route.rs | 12 ++++----- packages/router/src/hooks/use_router.rs | 12 ++++----- packages/router/src/service.rs | 33 +++++++++++------------- 12 files changed, 76 insertions(+), 81 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index e8886caf6..71c5d2198 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,4 +1,5 @@ use std::cell::Cell; +use std::rc::Rc; use crate::innerlude::{VComponent, VText}; use crate::mutations::Mutation; @@ -340,7 +341,7 @@ impl<'b> VirtualDom { } // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense - let boundary = match self.scopes[scope.0].has_context::() { + let boundary = match self.scopes[scope.0].has_context::>() { Some(boundary) => boundary, _ => return created, }; diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index cda8a9549..6ef3704d3 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -34,9 +34,9 @@ impl VirtualDom { } } - pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> Rc { + pub(crate) fn acquire_suspense_boundary<'a>(&'a self, id: ScopeId) -> Rc { self.scopes[id.0] - .consume_context::() + .consume_context::>() .unwrap() } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index b2863b9e5..79ce6f64a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -81,7 +81,7 @@ pub struct ScopeState { pub(crate) hook_list: RefCell>, pub(crate) hook_idx: Cell, - pub(crate) shared_contexts: RefCell>>, + pub(crate) shared_contexts: RefCell>>, pub(crate) tasks: Rc, pub(crate) spawned_tasks: HashSet, @@ -250,17 +250,18 @@ impl<'src> ScopeState { } /// Return any context of type T if it exists on this scope - pub fn has_context(&self) -> Option> { + pub fn has_context(&self) -> Option { self.shared_contexts .borrow() .get(&TypeId::of::())? - .clone() - .downcast() - .ok() + .downcast_ref::() + .cloned() } /// Try to retrieve a shared state with type `T` from any parent scope. - pub fn consume_context(&self) -> Option> { + /// + /// Clones the state if it exists. + pub fn consume_context(&self) -> Option { if let Some(this_ctx) = self.has_context() { return Some(this_ctx); } @@ -270,7 +271,7 @@ impl<'src> ScopeState { // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { - return shared.clone().downcast().ok(); + return shared.downcast_ref::().cloned(); } search_parent = parent.parent; } @@ -303,15 +304,14 @@ impl<'src> ScopeState { /// render!(div { "hello {state.0}" }) /// } /// ``` - pub fn provide_context(&self, value: T) -> Rc { - let mut contexts = self.shared_contexts.borrow_mut(); - contexts.insert(TypeId::of::(), Rc::new(value)); - contexts - .get(&TypeId::of::()) - .unwrap() - .clone() - .downcast() - .unwrap() + pub fn provide_context(&self, value: T) -> T { + let value2 = value.clone(); + + self.shared_contexts + .borrow_mut() + .insert(TypeId::of::(), Box::new(value)); + + value2 } /// Pushes the future onto the poll queue to be polled after the component renders. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index dbcba7548..57d7c10d4 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -249,10 +249,10 @@ impl VirtualDom { // This could be unexpected, so we might rethink this behavior later // // We *could* just panic if the suspense boundary is not found - root.provide_context(SuspenseContext::new(ScopeId(0))); + root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0)))); // Unlike react, we provide a default error boundary that just renders the error as a string - root.provide_context(ErrorBoundary::new(ScopeId(0))); + root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0)))); // the root element is always given element ID 0 since it's the container for the entire tree dom.elements.insert(ElementRef::null()); @@ -296,7 +296,7 @@ impl VirtualDom { /// currently suspended. pub fn is_scope_suspended(&self, id: ScopeId) -> bool { !self.scopes[id.0] - .consume_context::() + .consume_context::>() .unwrap() .waiting_on .borrow() @@ -527,7 +527,7 @@ impl VirtualDom { // first, unload any complete suspense trees for finished_fiber in self.finished_fibers.drain(..) { let scope = &mut self.scopes[finished_fiber.0]; - let context = scope.has_context::().unwrap(); + let context = scope.has_context::>().unwrap(); self.mutations .templates @@ -565,7 +565,7 @@ impl VirtualDom { // No placeholder necessary since this is a diff if !self.collected_leaves.is_empty() { let mut boundary = self.scopes[dirty.id.0] - .consume_context::() + .consume_context::>() .unwrap(); let boundary_mut = boundary.borrow_mut(); diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs index 267b71161..24fd70093 100644 --- a/packages/fermi/src/hooks/atom_root.rs +++ b/packages/fermi/src/hooks/atom_root.rs @@ -1,6 +1,7 @@ +use std::rc::Rc; + use crate::AtomRoot; use dioxus_core::ScopeState; -use std::rc::Rc; // Returns the atom root, initiaizing it at the root of the app if it does not exist. pub fn use_atom_root(cx: &ScopeState) -> &Rc { diff --git a/packages/hooks/src/usecontext.rs b/packages/hooks/src/usecontext.rs index 5d0f42363..a6e581fda 100644 --- a/packages/hooks/src/usecontext.rs +++ b/packages/hooks/src/usecontext.rs @@ -1,13 +1,18 @@ use dioxus_core::ScopeState; +use std::rc::Rc; -/// Consume some context in the tree -pub fn use_context(cx: &ScopeState) -> Option<&T> { - cx.use_hook(|| cx.consume_context::()).as_deref() +/// Consume some context in the tree, providing a sharable handle to the value +pub fn use_context(cx: &ScopeState) -> Option<&T> { + cx.use_hook(|| cx.consume_context::()).as_ref() } /// Provide some context via the tree and return a reference to it /// /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability. -pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { - cx.use_hook(|| cx.provide_context(f())) +pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { + cx.use_hook(|| { + let val = f(); + cx.provide_context(val.clone()); + val + }) } diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 1746ba5a9..4264e1ed0 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; - -use crate::{use_route, RouterCore}; +use crate::{use_route, RouterContext}; use dioxus::prelude::*; /// Props for the [`Link`](struct.Link.html) component. @@ -77,7 +75,7 @@ pub struct LinkProps<'a> { /// } /// ``` pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - let svc = cx.use_hook(|| cx.consume_context::>()); + let svc = use_context::(cx); let LinkProps { to, diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 04b8944e1..60b85d687 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -1,7 +1,5 @@ +use crate::{RouteContext, RouterContext}; use dioxus::prelude::*; -use std::sync::Arc; - -use crate::{RouteContext, RouterCore}; /// Props for the [`Route`](struct.Route.html) component. #[derive(Props)] @@ -27,15 +25,13 @@ pub struct RouteProps<'a> { /// ) /// ``` pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { - let router_root = cx - .use_hook(|| cx.consume_context::>()) - .as_ref() - .unwrap(); + let router_root = use_context::(cx).unwrap(); + let root_context = use_context::(cx); cx.use_hook(|| { // create a bigger, better, longer route if one above us exists - let total_route = match cx.consume_context::() { - Some(ctx) => ctx.total_route, + let total_route = match root_context { + Some(ctx) => ctx.total_route.clone(), None => cx.props.to.to_string(), }; @@ -46,7 +42,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { }); // submit our rout - router_root.register_total_route(route_context.total_route, cx.scope_id()); + router_root.register_total_route(route_context.total_route.clone(), cx.scope_id()); }); log::trace!("Checking Route: {:?}", cx.props.to); diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 008197d0f..53b4717ad 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -1,6 +1,5 @@ -use crate::{cfg::RouterCfg, RouterCore}; +use crate::{cfg::RouterCfg, RouterContext, RouterService}; use dioxus::prelude::*; -use std::sync::Arc; /// The props for the [`Router`](fn.Router.html) component. #[derive(Props)] @@ -21,7 +20,7 @@ pub struct RouterProps<'a> { /// /// This lets you easily implement redirects #[props(default)] - pub onchange: EventHandler<'a, Arc>, + pub onchange: EventHandler<'a, RouterContext>, /// Set the active class of all Link components contained in this router. /// @@ -40,15 +39,15 @@ pub struct RouterProps<'a> { /// Will fallback to HashRouter is BrowserRouter is not available, or through configuration. #[allow(non_snake_case)] pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element { - let svc = cx.use_hook(|| { - cx.provide_context(RouterCore::new( + let svc = use_context_provider(cx, || { + RouterService::new( &cx, RouterCfg { base_url: cx.props.base_url.map(|s| s.to_string()), active_class: cx.props.active_class.map(|s| s.to_string()), initial_url: cx.props.initial_url.clone(), }, - )) + ) }); // next time we run the rout_found will be filled diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 27f99dfea..2e6b2cd4e 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,4 +1,4 @@ -use crate::{ParsedRoute, RouteContext, RouterCore, RouterService}; +use crate::{ParsedRoute, RouteContext, RouterContext}; use dioxus::core::{ScopeId, ScopeState}; use std::{borrow::Cow, str::FromStr, sync::Arc}; use url::Url; @@ -7,11 +7,11 @@ use url::Url; /// context of a [`Router`]. If this function is called outside of a `Router` /// component it will panic. pub fn use_route(cx: &ScopeState) -> &UseRoute { - let handle = cx.use_hook(|| { - let router = cx - .consume_context::() - .expect("Cannot call use_route outside the scope of a Router component"); + let router = cx + .consume_context::() + .expect("Cannot call use_route outside the scope of a Router component"); + let handle = cx.use_hook(|| { let route_context = cx.consume_context::(); router.subscribe_onchange(cx.scope_id()); @@ -115,7 +115,7 @@ impl UseRoute { // and reveal our cached version of UseRoute to the component. struct UseRouteListener { state: UseRoute, - router: Arc, + router: RouterContext, scope: ScopeId, } diff --git a/packages/router/src/hooks/use_router.rs b/packages/router/src/hooks/use_router.rs index 4b4a12487..bc4402ecb 100644 --- a/packages/router/src/hooks/use_router.rs +++ b/packages/router/src/hooks/use_router.rs @@ -1,10 +1,8 @@ -use crate::RouterService; -use dioxus::core::ScopeState; +use crate::RouterContext; +use dioxus::{core::ScopeState, prelude::use_context}; /// This hook provides access to the `RouterService` for the app. -pub fn use_router(cx: &ScopeState) -> &RouterService { - cx.use_hook(|| { - cx.consume_context::() - .expect("Cannot call use_route outside the scope of a Router component") - }) +pub fn use_router(cx: &ScopeState) -> &RouterContext { + use_context::(cx) + .expect("Cannot call use_route outside the scope of a Router component") } diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 039d0033d..69ef19995 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -4,7 +4,7 @@ use crate::cfg::RouterCfg; use dioxus::core::{ScopeId, ScopeState, VirtualDom}; use std::any::Any; -use std::sync::Weak; +use std::rc::Weak; use std::{ cell::{Cell, RefCell}, collections::{HashMap, HashSet}, @@ -14,6 +14,9 @@ use std::{ }; use url::Url; +/// A clonable handle to the router +pub type RouterContext = Rc; + /// An abstraction over the platform's history API. /// /// The history is denoted using web-like semantics, with forward slashes delmitiing @@ -41,7 +44,7 @@ use url::Url; /// - On the web, this is a [`BrowserHistory`](https://docs.rs/gloo/0.3.0/gloo/history/struct.BrowserHistory.html). /// - On desktop, mobile, and SSR, this is just a Vec of Strings. Currently on /// desktop, there is no way to tap into forward/back for the app unless explicitly set. -pub struct RouterCore { +pub struct RouterService { pub(crate) route_found: Cell>, pub(crate) stack: RefCell>>, @@ -61,9 +64,6 @@ pub struct RouterCore { pub(crate) cfg: RouterCfg, } -/// A shared type for the RouterCore. -pub type RouterService = Arc; - /// A route is a combination of window title, saved state, and a URL. #[derive(Debug, Clone)] pub struct ParsedRoute { @@ -77,8 +77,8 @@ pub struct ParsedRoute { pub serialized_state: Option, } -impl RouterCore { - pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> Arc { +impl RouterService { + pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> RouterContext { #[cfg(feature = "web")] let history = Box::new(web::new()); @@ -99,7 +99,7 @@ impl RouterCore { None => Arc::new(history.init_location()), }; - let svc = Arc::new(Self { + let svc = Rc::new(Self { cfg, regen_any_route: cx.schedule_update_any(), router_id: cx.scope_id(), @@ -111,7 +111,7 @@ impl RouterCore { history, }); - svc.history.attach_listeners(Arc::downgrade(&svc)); + svc.history.attach_listeners(Rc::downgrade(&svc)); svc } @@ -247,12 +247,9 @@ impl RouterCore { /// that owns the router. /// /// This might change in the future. -pub fn get_router_from_vdom( - dom: &VirtualDom, - target_scope: Option, -) -> Option> { - dom.get_scope(target_scope.unwrap_or(ScopeId(0))) - .and_then(|scope| scope.consume_context::>()) +pub fn get_router_from_vdom(dom: &VirtualDom, target_scope: ScopeId) -> Option { + dom.get_scope(target_scope) + .and_then(|scope| scope.consume_context::()) } fn clean_route(route: String) -> String { @@ -319,7 +316,7 @@ pub(crate) trait RouterProvider { fn replace(&self, route: &ParsedRoute); fn native_location(&self) -> Box; fn init_location(&self) -> ParsedRoute; - fn attach_listeners(&self, svc: Weak); + fn attach_listeners(&self, svc: Weak); } #[cfg(not(feature = "web"))] @@ -350,7 +347,7 @@ mod hash { fn replace(&self, _route: &ParsedRoute) {} - fn attach_listeners(&self, _svc: Weak) {} + fn attach_listeners(&self, _svc: Weak) {} } } @@ -418,7 +415,7 @@ mod web { } } - fn attach_listeners(&self, svc: std::sync::Weak) { + fn attach_listeners(&self, svc: std::sync::Weak) { self._listener.set(Some(EventListener::new( &web_sys::window().unwrap(), "popstate",