Sfoglia il codice sorgente

try not double boxing

Evan Almloff 1 anno fa
parent
commit
dab87c0144

+ 13 - 8
packages/core/src/any_props.rs

@@ -1,7 +1,7 @@
-use crate::{nodes::RenderReturn, Component};
+use crate::{nodes::RenderReturn, ComponentFunction};
 use std::{any::Any, panic::AssertUnwindSafe};
 
-pub type BoxedAnyProps = Box<dyn AnyProps>;
+pub(crate) type BoxedAnyProps = Box<dyn AnyProps>;
 
 /// A trait that essentially allows VComponentProps to be used generically
 pub(crate) trait AnyProps {
@@ -12,8 +12,8 @@ pub(crate) trait AnyProps {
 }
 
 /// Create a new boxed props object.
-pub fn new_any_props<P: 'static + Clone>(
-    render_fn: Component<P>,
+pub(crate) fn new_any_props<F: ComponentFunction<P, M>, P: Clone + 'static, M: 'static>(
+    render_fn: F,
     memo: fn(&P, &P) -> bool,
     props: P,
     name: &'static str,
@@ -23,17 +23,21 @@ pub fn new_any_props<P: 'static + Clone>(
         memo,
         props,
         name,
+        phantom: std::marker::PhantomData,
     })
 }
 
-struct VProps<P> {
-    render_fn: Component<P>,
+struct VProps<F: ComponentFunction<P, M>, P, M> {
+    render_fn: F,
     memo: fn(&P, &P) -> bool,
     props: P,
     name: &'static str,
+    phantom: std::marker::PhantomData<M>,
 }
 
-impl<P: Clone + 'static> AnyProps for VProps<P> {
+impl<F: ComponentFunction<P, M> + Clone, P: Clone + 'static, M: 'static> AnyProps
+    for VProps<F, P, M>
+{
     fn memoize(&self, other: &dyn Any) -> bool {
         match other.downcast_ref::<P>() {
             Some(other) => (self.memo)(&self.props, other),
@@ -47,7 +51,7 @@ impl<P: Clone + 'static> AnyProps for VProps<P> {
 
     fn render(&self) -> RenderReturn {
         let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
-            (self.render_fn)(self.props.clone())
+            self.render_fn.rebuild(self.props.clone())
         }));
 
         match res {
@@ -67,6 +71,7 @@ impl<P: Clone + 'static> AnyProps for VProps<P> {
             memo: self.memo,
             props: self.props.clone(),
             name: self.name,
+            phantom: std::marker::PhantomData,
         })
     }
 }

+ 1 - 3
packages/core/src/nodes.rs

@@ -520,7 +520,7 @@ pub struct VComponent {
 
 impl VComponent {
     /// Create a new [`VComponent`] variant
-    pub fn new<P, M>(
+    pub fn new<P, M: 'static>(
         component: impl ComponentFunction<P, M>,
         props: P,
         fn_name: &'static str,
@@ -528,9 +528,7 @@ impl VComponent {
     where
         P: Properties + 'static,
     {
-        let component = Rc::new(component);
         let render_fn = component.id();
-        let component = component.as_component();
         let props = new_any_props(component, <P as Properties>::memoize, props, fn_name);
 
         VComponent {

+ 24 - 15
packages/core/src/platform.rs

@@ -1,6 +1,9 @@
 use std::any::Any;
 
-use crate::{properties::ComponentFunction, Component, VirtualDom};
+use crate::{
+    properties::{ComponentFunction, RootProps},
+    VComponent, VirtualDom,
+};
 
 /// A boxed object that can be injected into a component's context.
 pub struct BoxedContext(Box<dyn ClonableAny>);
@@ -40,32 +43,38 @@ impl<T: Any + Clone> ClonableAny for T {
 }
 
 /// The platform-independent part of the config needed to launch an application.
-pub struct CrossPlatformConfig<Props: Clone + 'static> {
+pub struct CrossPlatformConfig {
     /// The root component function.
-    pub component: Component<Props>,
-    /// The props for the root component.
-    pub props: Props,
+    component: VComponent,
     /// The contexts to provide to the root component.
-    pub root_contexts: Vec<BoxedContext>,
+    root_contexts: Vec<BoxedContext>,
 }
 
-impl<Props: Clone + 'static> CrossPlatformConfig<Props> {
+impl CrossPlatformConfig {
     /// Create a new cross-platform config.
-    pub fn new<M>(
+    pub fn new<Props: Clone + 'static, M: 'static>(
         component: impl ComponentFunction<Props, M>,
         props: Props,
         root_contexts: Vec<BoxedContext>,
     ) -> Self {
         Self {
-            component: ComponentFunction::as_component(std::rc::Rc::new(component)),
-            props,
+            component: VComponent::new(
+                move |props: RootProps<Props>| component.rebuild(props.0),
+                RootProps(props),
+                "root",
+            ),
             root_contexts,
         }
     }
 
+    /// Push a new context into the root component's context.
+    pub fn push_context<T: Any + Clone + 'static>(&mut self, context: T) {
+        self.root_contexts.push(BoxedContext::new(context));
+    }
+
     /// Build a virtual dom from the config.
     pub fn build_vdom(self) -> VirtualDom {
-        let mut vdom = VirtualDom::new_with_props(self.component, self.props);
+        let mut vdom = VirtualDom::new_with_component(self.component);
 
         for context in self.root_contexts {
             vdom.insert_boxed_root_context(context);
@@ -76,18 +85,18 @@ impl<Props: Clone + 'static> CrossPlatformConfig<Props> {
 }
 
 /// A builder to launch a specific platform.
-pub trait PlatformBuilder<Props: Clone + 'static> {
+pub trait PlatformBuilder {
     /// The platform-specific config needed to launch an application.
     type Config: Default;
 
     /// Launch the app.
-    fn launch(config: CrossPlatformConfig<Props>, platform_config: Self::Config);
+    fn launch(config: CrossPlatformConfig, platform_config: Self::Config);
 }
 
-impl<Props: Clone + 'static> PlatformBuilder<Props> for () {
+impl PlatformBuilder for () {
     type Config = ();
 
-    fn launch(_: CrossPlatformConfig<Props>, _: Self::Config) {
+    fn launch(_: CrossPlatformConfig, _: Self::Config) {
         panic!("No platform is currently enabled. Please enable a platform feature for the dioxus crate.");
     }
 }

+ 36 - 11
packages/core/src/properties.rs

@@ -1,4 +1,4 @@
-use std::{any::TypeId, rc::Rc};
+use std::any::TypeId;
 
 use crate::innerlude::*;
 
@@ -45,6 +45,31 @@ impl Properties for () {
     }
 }
 
+/// Root properties never need to be memoized, so we can use a dummy implementation.
+pub(crate) struct RootProps<P>(pub P);
+
+impl<P> Clone for RootProps<P>
+where
+    P: Clone,
+{
+    fn clone(&self) -> Self {
+        Self(self.0.clone())
+    }
+}
+
+impl<P> Properties for RootProps<P>
+where
+    P: Clone + 'static,
+{
+    type Builder = P;
+    fn builder() -> Self::Builder {
+        todo!()
+    }
+    fn memoize(&self, _other: &Self) -> bool {
+        true
+    }
+}
+
 // We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
 // that the macros use to anonymously complete prop construction.
 pub struct EmptyBuilder;
@@ -62,35 +87,35 @@ where
 }
 
 /// Any component that implements the `ComponentFn` trait can be used as a component.
-pub trait ComponentFunction<Props, Marker = ()>: 'static {
+pub trait ComponentFunction<Props, Marker = ()>: Clone + 'static {
     /// Get the type id of the component.
     fn id(&self) -> TypeId {
         TypeId::of::<Self>()
     }
 
     /// Convert the component to a function that takes props and returns an element.
-    fn as_component(self: Rc<Self>) -> Component<Props>;
+    fn rebuild(&self, props: Props) -> Element;
 }
 
 /// Accept pre-formed component render functions as components
 impl<P: 'static> ComponentFunction<P> for Component<P> {
-    fn as_component(self: Rc<Self>) -> Component<P> {
-        self.as_ref().clone()
+    fn rebuild(&self, props: P) -> Element {
+        (self)(props)
     }
 }
 
 /// Accept any callbacks that take props
-impl<F: Fn(P) -> Element + 'static, P> ComponentFunction<P> for F {
-    fn as_component(self: Rc<Self>) -> Component<P> {
-        self
+impl<F: Fn(P) -> Element + Clone + 'static, P> ComponentFunction<P> for F {
+    fn rebuild(&self, props: P) -> Element {
+        self(props)
     }
 }
 
 /// Accept any callbacks that take no props
 pub struct EmptyMarker;
-impl<F: Fn() -> Element + 'static> ComponentFunction<(), EmptyMarker> for F {
-    fn as_component(self: Rc<Self>) -> Rc<dyn Fn(()) -> Element> {
-        Rc::new(move |_| self())
+impl<F: Fn() -> Element + Clone + 'static> ComponentFunction<(), EmptyMarker> for F {
+    fn rebuild(&self, _: ()) -> Element {
+        self()
     }
 }
 

+ 48 - 12
packages/core/src/virtual_dom.rs

@@ -3,7 +3,6 @@
 //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
 
 use crate::{
-    any_props::new_any_props,
     arena::ElementId,
     innerlude::{
         DirtyScope, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeState, VNodeMount,
@@ -11,10 +10,10 @@ use crate::{
     },
     nodes::RenderReturn,
     nodes::{Template, TemplateId},
-    properties::ComponentFunction,
+    properties::RootProps,
     runtime::{Runtime, RuntimeGuard},
     scopes::ScopeId,
-    AttributeValue, BoxedContext, Element, Event, Mutations, Task,
+    AttributeValue, BoxedContext, ComponentFunction, Element, Event, Mutations, Task, VComponent,
 };
 use futures_util::{pin_mut, StreamExt};
 use rustc_hash::{FxHashMap, FxHashSet};
@@ -230,6 +229,49 @@ impl VirtualDom {
         Self::new_with_props(app, ())
     }
 
+    /// Create a new VirtualDom with the given properties for the root component.
+    ///
+    /// # Description
+    ///
+    /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
+    ///
+    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
+    /// to toss out the entire tree.
+    ///
+    ///
+    /// # Example
+    /// ```rust, ignore
+    /// #[derive(PartialEq, Props)]
+    /// struct SomeProps {
+    ///     name: &'static str
+    /// }
+    ///
+    /// fn Example(cx: SomeProps) -> Element  {
+    ///     rsx!{ div{ "hello {cx.name}" } })
+    /// }
+    ///
+    /// let dom = VirtualDom::new(Example);
+    /// ```
+    ///
+    /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
+    ///
+    /// ```rust, ignore
+    /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
+    /// let mutations = dom.rebuild();
+    /// ```
+    pub fn new_with_props<P: Clone + 'static, M>(
+        root: impl ComponentFunction<P, M>,
+        root_props: P,
+    ) -> Self {
+        let vcomponent = VComponent::new(
+            move |props: RootProps<P>| root.rebuild(props.0),
+            RootProps(root_props),
+            "root",
+        );
+
+        Self::new_with_component(vcomponent)
+    }
+
     /// Create a new virtualdom and build it immediately
     pub fn prebuilt(app: fn() -> Element) -> Self {
         let mut dom = Self::new(app);
@@ -264,13 +306,10 @@ impl VirtualDom {
     /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
     ///
     /// ```rust, ignore
-    /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
+    /// let mut dom = VirtualDom::new_from_root(VComponent::new(Example, SomeProps { name: "jane" }, "Example"));
     /// let mutations = dom.rebuild();
     /// ```
-    pub fn new_with_props<P: Clone + 'static, M>(
-        root: impl ComponentFunction<P, M>,
-        root_props: P,
-    ) -> Self {
+    pub fn new_with_component(root: VComponent) -> Self {
         let (tx, rx) = futures_channel::mpsc::unbounded();
 
         let mut dom = Self {
@@ -285,10 +324,7 @@ impl VirtualDom {
             suspended_scopes: Default::default(),
         };
 
-        let root = dom.new_scope(
-            new_any_props(Rc::new(root).as_component(), |_, _| true, root_props, "app"),
-            "app",
-        );
+        let root = dom.new_scope(root.props, "app");
 
         // Unlike react, we provide a default error boundary that just renders the error as a string
         root.context()

+ 4 - 5
packages/desktop/src/app.rs

@@ -18,7 +18,6 @@ use dioxus_html::{
 use std::{
     cell::{Cell, RefCell},
     collections::HashMap,
-    marker::PhantomData,
     rc::Rc,
     sync::Arc,
 };
@@ -29,10 +28,10 @@ use tao::{
 };
 
 /// The single top-level object that manages all the running windows, assets, shortcuts, etc
-pub(crate) struct App<Props: Clone + 'static> {
+pub(crate) struct App {
     // move the props into a cell so we can pop it out later to create the first window
     // iOS panics if we create a window before the event loop is started, so we toss them into a cell
-    pub(crate) dioxus_config: Cell<Option<CrossPlatformConfig<Props>>>,
+    pub(crate) dioxus_config: Cell<Option<CrossPlatformConfig>>,
     pub(crate) cfg: Cell<Option<Config>>,
 
     // Stuff we need mutable access to
@@ -59,10 +58,10 @@ pub struct SharedContext {
     pub(crate) target: EventLoopWindowTarget<UserWindowEvent>,
 }
 
-impl<Props: Clone + 'static> App<Props> {
+impl App {
     pub fn new(
         cfg: Config,
-        dioxus_config: CrossPlatformConfig<Props>,
+        dioxus_config: CrossPlatformConfig,
     ) -> (EventLoop<UserWindowEvent>, Self) {
         let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
 

+ 3 - 6
packages/desktop/src/launch.rs

@@ -10,10 +10,7 @@ use tao::event::{Event, StartCause, WindowEvent};
 ///
 /// This will block the main thread, and *must* be spawned on the main thread. This function does not assume any runtime
 /// and is equivalent to calling launch_with_props with the tokio feature disabled.
-pub fn launch_with_props_blocking<Props: Clone + 'static>(
-    dioxus_cfg: CrossPlatformConfig<Props>,
-    desktop_config: Config,
-) {
+pub fn launch_with_props_blocking(dioxus_cfg: CrossPlatformConfig, desktop_config: Config) {
     let (event_loop, mut app) = App::new(desktop_config, dioxus_cfg);
 
     event_loop.run(move |window_event, _, control_flow| {
@@ -53,10 +50,10 @@ pub fn launch_with_props_blocking<Props: Clone + 'static>(
 /// The desktop renderer platform
 pub struct DesktopPlatform;
 
-impl<Props: Clone + 'static> PlatformBuilder<Props> for DesktopPlatform {
+impl PlatformBuilder for DesktopPlatform {
     type Config = Config;
 
-    fn launch(config: dioxus_core::CrossPlatformConfig<Props>, platform_config: Self::Config) {
+    fn launch(config: dioxus_core::CrossPlatformConfig, platform_config: Self::Config) {
         #[cfg(feature = "tokio")]
         tokio::runtime::Builder::new_multi_thread()
             .enable_all()

+ 25 - 26
packages/dioxus/src/launch.rs

@@ -7,19 +7,17 @@ use dioxus_core::prelude::*;
 use dioxus_core::{BoxedContext, CrossPlatformConfig, PlatformBuilder};
 
 /// A builder for a fullstack app.
-pub struct LaunchBuilder<Props: Clone + 'static, Platform: PlatformBuilder<Props> = CurrentPlatform>
-{
-    cross_platform_config: CrossPlatformConfig<Props>,
-    platform_config: Option<<Platform as PlatformBuilder<Props>>::Config>,
+pub struct LaunchBuilder<Platform: PlatformBuilder = CurrentPlatform> {
+    cross_platform_config: CrossPlatformConfig,
+    platform_config: Option<<Platform as PlatformBuilder>::Config>,
 }
 
 // Default platform builder
-impl<Props: Clone + 'static> LaunchBuilder<Props> {
+impl LaunchBuilder {
     /// Create a new builder for your application. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.
-    pub fn new<M>(component: impl ComponentFunction<Props, M>) -> Self
-    where
-        Props: Default,
-    {
+    pub fn new<Props: Clone + Default + 'static, M: 'static>(
+        component: impl ComponentFunction<Props, M>,
+    ) -> Self {
         Self {
             cross_platform_config: CrossPlatformConfig::new(
                 component,
@@ -29,28 +27,29 @@ impl<Props: Clone + 'static> LaunchBuilder<Props> {
             platform_config: None,
         }
     }
-}
 
-impl<Props: Clone + 'static, Platform: PlatformBuilder<Props>> LaunchBuilder<Props, Platform> {
-    /// Pass some props to your application.
-    pub fn props(mut self, props: Props) -> Self {
-        self.cross_platform_config.props = props;
-        self
+    /// Create a new builder for your application with some root props. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.
+    pub fn new_with_props<Props: Clone + 'static, M: 'static>(
+        component: impl ComponentFunction<Props, M>,
+        props: Props,
+    ) -> Self {
+        Self {
+            cross_platform_config: CrossPlatformConfig::new(component, props, Default::default()),
+            platform_config: None,
+        }
     }
+}
 
+impl<Platform: PlatformBuilder> LaunchBuilder<Platform> {
     /// Inject state into the root component's context.
     pub fn context(mut self, state: impl Any + Clone + 'static) -> Self {
         self.cross_platform_config
-            .root_contexts
-            .push(BoxedContext::new(state));
+            .push_context(BoxedContext::new(state));
         self
     }
 
     /// Provide a platform-specific config to the builder.
-    pub fn cfg(
-        mut self,
-        config: impl Into<Option<<Platform as PlatformBuilder<Props>>::Config>>,
-    ) -> Self {
+    pub fn cfg(mut self, config: impl Into<Option<<Platform as PlatformBuilder>::Config>>) -> Self {
         if let Some(config) = config.into() {
             self.platform_config = Some(config);
         }
@@ -68,7 +67,7 @@ impl<Props: Clone + 'static, Platform: PlatformBuilder<Props>> LaunchBuilder<Pro
 }
 
 #[cfg(feature = "web")]
-impl<Props: Clone + 'static> LaunchBuilder<Props, dioxus_web::WebPlatform> {
+impl LaunchBuilder<dioxus_web::WebPlatform> {
     /// Launch your web application.
     pub fn launch_web(self) {
         dioxus_web::WebPlatform::launch(
@@ -79,7 +78,7 @@ impl<Props: Clone + 'static> LaunchBuilder<Props, dioxus_web::WebPlatform> {
 }
 
 #[cfg(feature = "desktop")]
-impl<Props: Clone + 'static> LaunchBuilder<Props, dioxus_desktop::DesktopPlatform> {
+impl LaunchBuilder<dioxus_desktop::DesktopPlatform> {
     /// Launch your desktop application.
     pub fn launch_desktop(self) {
         dioxus_desktop::DesktopPlatform::launch(
@@ -97,7 +96,7 @@ type CurrentPlatform = dioxus_web::WebPlatform;
 type CurrentPlatform = ();
 
 /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
-pub fn launch<Props, Marker>(component: impl ComponentFunction<Props, Marker>)
+pub fn launch<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>)
 where
     Props: Default + Clone + 'static,
 {
@@ -106,7 +105,7 @@ where
 
 #[cfg(feature = "web")]
 /// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options.
-pub fn launch_web<Props, Marker>(component: impl ComponentFunction<Props, Marker>)
+pub fn launch_web<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>)
 where
     Props: Default + Clone + 'static,
 {
@@ -115,7 +114,7 @@ where
 
 #[cfg(feature = "desktop")]
 /// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options.
-pub fn launch_desktop<Props, Marker>(component: impl ComponentFunction<Props, Marker>)
+pub fn launch_desktop<Props, Marker: 'static>(component: impl ComponentFunction<Props, Marker>)
 where
     Props: Default + Clone + 'static,
 {

+ 6 - 8
packages/router-macro/src/lib.rs

@@ -609,15 +609,13 @@ impl RouteEnum {
 
         quote! {
             impl dioxus_core::ComponentFunction<#props> for #name {
-                fn as_component(self: ::std::rc::Rc<Self>) -> Component<#props> {
-                    ::std::rc::Rc::new(move |props| {
-                        let initial_route = self.as_ref().clone();
-                        rsx! {
-                            ::dioxus_router::prelude::Router::<#name> {
-                                config: move || props.take().initial_route(initial_route)
-                            }
+                fn rebuild(&self, props: #props) -> dioxus_core::Element {
+                    let initial_route = self.clone();
+                    rsx! {
+                        ::dioxus_router::prelude::Router::<#name> {
+                            config: move || props.take().initial_route(initial_route)
                         }
-                    })
+                    }
                 }
             }
         }

+ 1 - 4
packages/web/src/lib.rs

@@ -99,10 +99,7 @@ mod rehydrate;
 ///     wasm_bindgen_futures::spawn_local(app_fut);
 /// }
 /// ```
-pub async fn run_with_props<Props: Clone + 'static>(
-    dioxus_config: CrossPlatformConfig<Props>,
-    web_config: Config,
-) {
+pub async fn run_with_props(dioxus_config: CrossPlatformConfig, web_config: Config) {
     tracing::info!("Starting up");
 
     let mut dom = dioxus_config.build_vdom();

+ 2 - 2
packages/web/src/platform.rs

@@ -5,10 +5,10 @@ use crate::Config;
 /// The web renderer platform
 pub struct WebPlatform;
 
-impl<Props: Clone + 'static> PlatformBuilder<Props> for WebPlatform {
+impl PlatformBuilder for WebPlatform {
     type Config = Config;
 
-    fn launch(config: CrossPlatformConfig<Props>, platform_config: Self::Config) {
+    fn launch(config: CrossPlatformConfig, platform_config: Self::Config) {
         wasm_bindgen_futures::spawn_local(async move {
             crate::run_with_props(config, platform_config).await;
         });