From c94af9538b623404368c507555ef0b69a8389cfd Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 16 Jan 2024 15:07:22 -0800 Subject: [PATCH] Remove phantom markers and just default to Rc Element> where it makes sense --- packages/core/src/any_props.rs | 21 +++--- packages/core/src/lib.rs | 12 ++-- packages/core/src/nodes.rs | 25 ++++--- packages/core/src/platform.rs | 39 ++++------- packages/core/src/properties.rs | 117 +++++++++---------------------- packages/core/src/virtual_dom.rs | 18 ++--- packages/desktop/src/app.rs | 22 ++---- packages/desktop/src/launch.rs | 13 +--- packages/dioxus/src/launch.rs | 75 +++++--------------- 9 files changed, 108 insertions(+), 234 deletions(-) diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index edf115de5..1af7bb5b6 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,5 +1,5 @@ -use crate::{nodes::RenderReturn, properties::ComponentFunction}; -use std::{any::Any, marker::PhantomData, ops::Deref, panic::AssertUnwindSafe}; +use crate::{nodes::RenderReturn, Component}; +use std::{any::Any, ops::Deref, panic::AssertUnwindSafe}; /// A boxed version of AnyProps that can be cloned pub(crate) struct BoxedAnyProps { @@ -38,17 +38,16 @@ pub(crate) trait AnyProps { fn duplicate(&self) -> Box; } -pub(crate) struct VProps, Phantom: 'static> { - pub render_fn: F, +pub(crate) struct VProps { + pub render_fn: Component

, pub memo: fn(&P, &P) -> bool, pub props: P, pub name: &'static str, - phantom: PhantomData, } -impl, Phantom: 'static> VProps { +impl VProps

{ pub(crate) fn new( - render_fn: F, + render_fn: Component

, memo: fn(&P, &P) -> bool, props: P, name: &'static str, @@ -58,14 +57,11 @@ impl, Phantom: 'static> VPr memo, props, name, - phantom: PhantomData, } } } -impl, Phantom> AnyProps - for VProps -{ +impl AnyProps for VProps

{ fn memoize(&self, other: &dyn Any) -> bool { match other.downcast_ref::

() { Some(other) => (self.memo)(&self.props, other), @@ -80,7 +76,7 @@ impl, Phantom> AnyP fn render(&self) -> RenderReturn { let res = std::panic::catch_unwind(AssertUnwindSafe(move || { // Call the render function directly - self.render_fn.call(self.props.clone()) + (self.render_fn)(self.props.clone()) })); match res { @@ -100,7 +96,6 @@ impl, Phantom> AnyP memo: self.memo, props: self.props.clone(), name: self.name, - phantom: PhantomData, }) } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 2a22dff00..eb54bed1a 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -70,12 +70,12 @@ pub(crate) mod innerlude { /// Example {} /// ) /// ``` - pub type Component

= fn(P) -> Element; + pub type Component

= std::rc::Rc Element>; } pub use crate::innerlude::{ fc_to_builder, generation, schedule_update, schedule_update_any, use_hook, vdom_is_rendering, - AnyValue, Attribute, AttributeValue, BoxedContext, CapturedError, Component, ComponentFunction, + AnyValue, Attribute, AttributeValue, BoxedContext, CapturedError, Component, CrossPlatformConfig, DynamicNode, Element, ElementId, Event, Fragment, HasAttributes, IntoDynNode, Mutation, Mutations, NoOpMutations, PlatformBuilder, Properties, RenderReturn, ScopeId, ScopeState, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, @@ -90,9 +90,9 @@ pub mod prelude { consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation, has_context, needs_update, parent_scope, provide_context, provide_root_context, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend, - try_consume_context, use_error_boundary, use_hook, AnyValue, Attribute, Component, Element, - ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, - IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, Task, Template, - TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner, VirtualDom, + try_consume_context, use_error_boundary, use_hook, AnyValue, Attribute, Component, + ComponentFn, Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, + IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, + Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner, VirtualDom, }; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9cdfd525a..edea9d025 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,13 +1,14 @@ -use crate::innerlude::{ElementRef, EventHandler, MountId}; -use crate::properties::ComponentFunction; use crate::{ any_props::{BoxedAnyProps, VProps}, innerlude::ScopeState, }; use crate::{arena::ElementId, Element, Event}; +use crate::{ + innerlude::{ElementRef, EventHandler, MountId}, + properties::ComponentFn, +}; use crate::{Properties, VirtualDom}; use core::panic; -use std::ops::Deref; use std::rc::Rc; use std::vec; use std::{ @@ -15,6 +16,7 @@ use std::{ cell::Cell, fmt::{Arguments, Debug}, }; +use std::{ffi::c_void, ops::Deref}; pub type TemplateId = &'static str; @@ -511,7 +513,7 @@ pub struct VComponent { /// The function pointer of the component, known at compile time /// /// It is possible that components get folded at compile time, so these shouldn't be really used as a key - pub(crate) render_fn: TypeId, + pub(crate) render_fn: *const c_void, pub(crate) props: BoxedAnyProps, } @@ -531,22 +533,19 @@ impl VComponent { /// fn(Props) -> Element; /// async fn(Scope>) -> Element; /// ``` - pub fn new + 'static, P: 'static>( - component: F, - props: F::Props, - fn_name: &'static str, - ) -> Self + pub fn new(component: impl ComponentFn, props: P, fn_name: &'static str) -> Self where // The properties must be valid until the next bump frame - F::Props: Properties + 'static, + P: Properties + 'static, { - let render_fn_id = TypeId::of::(); - let vcomp = VProps::new(component, F::Props::memoize, props, fn_name); + let component = Rc::new(component).as_component(); + let render_fn = component.as_ref() as *const _ as *const c_void; + let vcomp = VProps::new(component,

::memoize, props, fn_name); VComponent { name: fn_name, - render_fn: render_fn_id, props: BoxedAnyProps::new(vcomp), + render_fn, } } diff --git a/packages/core/src/platform.rs b/packages/core/src/platform.rs index 275d1a72b..79a24151a 100644 --- a/packages/core/src/platform.rs +++ b/packages/core/src/platform.rs @@ -1,6 +1,6 @@ -use std::{any::Any, marker::PhantomData}; +use std::any::Any; -use crate::{ComponentFunction, VirtualDom}; +use crate::{properties::ComponentFn, Component, VirtualDom}; /// A boxed object that can be injected into a component's context. pub struct BoxedContext(Box); @@ -40,33 +40,26 @@ impl ClonableAny for T { } /// The platform-independent part of the config needed to launch an application. -pub struct CrossPlatformConfig< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, -> { +pub struct CrossPlatformConfig { /// The root component function. - pub component: Component, + pub component: Component, /// The props for the root component. pub props: Props, /// The contexts to provide to the root component. pub root_contexts: Vec, - _phantom: PhantomData, } -impl< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - > CrossPlatformConfig -{ +impl CrossPlatformConfig { /// Create a new cross-platform config. - pub fn new(component: Component, props: Props, root_contexts: Vec) -> Self { + pub fn new( + component: impl ComponentFn, + props: Props, + root_contexts: Vec, + ) -> Self { Self { - component, + component: ComponentFn::as_component(std::rc::Rc::new(component)), props, root_contexts, - _phantom: PhantomData, } } @@ -88,19 +81,13 @@ pub trait PlatformBuilder { type Config: Default; /// Launch the app. - fn launch, Phantom: 'static>( - config: CrossPlatformConfig, - platform_config: Self::Config, - ); + fn launch(config: CrossPlatformConfig, platform_config: Self::Config); } impl PlatformBuilder for () { type Config = (); - fn launch, Phantom: 'static>( - _: CrossPlatformConfig, - _: Self::Config, - ) { + fn launch(_: CrossPlatformConfig, _: Self::Config) { panic!("No platform is currently enabled. Please enable a platform feature for the dioxus crate."); } } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 8413d35ca..73e36ae4b 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -1,3 +1,5 @@ +use std::rc::Rc; + use crate::innerlude::*; /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus @@ -29,11 +31,7 @@ pub trait Properties: Clone + Sized + 'static { /// Create a builder for this component. fn builder() -> Self::Builder; - /// Memoization can only happen if the props are valid for the 'static lifetime - /// - /// # Safety - /// The user must know if their props are static, but if they make a mistake, UB happens - /// Therefore it's unsafe to memoize. + /// Compare two props to see if they are memoizable. fn memoize(&self, other: &Self) -> bool; } @@ -56,98 +54,51 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder, P>( - _: F, -) -> <>::Props as Properties>::Builder +pub fn fc_to_builder(_: impl ComponentFn) ->

::Builder where - F::Props: Properties, + P: Properties, { - F::Props::builder() + P::builder() } -/// Every component used in rsx must implement the `ComponentFunction` trait. This trait tells dioxus how your component should be rendered. -/// -/// Dioxus automatically implements this trait for any function that either takes no arguments or a single props argument and returns an Element. -/// -/// ## Example -/// -/// For components that take no props: -/// -/// ```rust -/// fn app() -> Element { -/// rsx! { -/// div {} -/// } -/// } -/// ``` -/// -/// For props that take a props struct: -/// -/// ```rust -/// #[derive(Props, PartialEq, Clone)] -/// struct MyProps { -/// data: String -/// } -/// -/// fn app(props: MyProps) -> Element { -/// rsx! { -/// div { -/// "{props.data}" -/// } -/// } -/// } -/// ``` -/// -/// Or you can use the #[component] macro to automatically implement create the props struct: -/// -/// ```rust -/// #[component] -/// fn app(data: String) -> Element { -/// rsx! { -/// div { -/// "{data}" -/// } -/// } -/// } -/// ``` -/// -/// > Note: If you get an error about the `ComponentFunction` trait not being implemented: make sure your props implements the `Properties` trait or if you would like to declare your props inline, make sure you use the #[component] macro on your function. -pub trait ComponentFunction

: Clone + 'static { - /// The props type for this component. - type Props: 'static; - - /// Run the component function with the given props. - fn call(&self, props: Self::Props) -> Element; +/// Any component that implements the `ComponentFn` trait can be used as a component. +pub trait ComponentFn { + /// Convert the component to a function that takes props and returns an element. + fn as_component(self: Rc) -> Component; } -impl Element + Clone + 'static> ComponentFunction<(T,)> for F { - type Props = T; - - fn call(&self, props: T) -> Element { - self(props) +/// Accept pre-formed component render functions as components +impl

ComponentFn for Component

{ + fn as_component(self: Rc) -> Component

{ + self.as_ref().clone() } } -#[doc(hidden)] -pub struct ZeroElementMarker; -impl Element + Clone + 'static> ComponentFunction for F { - type Props = (); +/// Accept any callbacks that take props +impl Element + 'static, P> ComponentFn for F { + fn as_component(self: Rc) -> Component

{ + self + } +} - fn call(&self, _: ()) -> Element { - self() +/// Accept any callbacks that take no props +pub struct EmptyMarker; +impl Element + 'static> ComponentFn<(), EmptyMarker> for F { + fn as_component(self: Rc) -> Rc Element> { + Rc::new(move |_| self()) } } #[test] -fn test_empty_builder() { - fn app() -> Element { - unimplemented!() +fn it_works_maybe() { + fn test(_: ()) -> Element { + todo!() } - fn app2(_: ()) -> Element { - unimplemented!() + fn test2() -> Element { + todo!() } - let builder = fc_to_builder(app); - builder.build(); - let builder = fc_to_builder(app2); - builder.build(); + + let callable: Rc> = Rc::new(test) as Rc>; + let callable2: Rc> = + Rc::new(test2) as Rc>; } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1bf38b101..724fae850 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -11,6 +11,7 @@ use crate::{ }, nodes::RenderReturn, nodes::{Template, TemplateId}, + properties::ComponentFn, runtime::{Runtime, RuntimeGuard}, scopes::ScopeId, AttributeValue, BoxedContext, Element, Event, Mutations, @@ -227,7 +228,7 @@ impl VirtualDom { /// /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it. pub fn new(app: fn() -> Element) -> Self { - Self::new_with_props(app, ()) + Self::new_with_props(move || app(), ()) } /// Create a new virtualdom and build it immediately @@ -267,12 +268,8 @@ impl VirtualDom { /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mutations = dom.rebuild(); /// ``` - pub fn new_with_props< - F: crate::ComponentFunction, - P: Clone + 'static, - Phantom: 'static, - >( - root: F, + pub fn new_with_props( + root: impl ComponentFn, root_props: P, ) -> Self { let (tx, rx) = futures_channel::mpsc::unbounded(); @@ -290,7 +287,12 @@ impl VirtualDom { }; let root = dom.new_scope( - BoxedAnyProps::new(VProps::new(root, |_, _| true, root_props, "root")), + BoxedAnyProps::new(VProps::new( + Rc::new(root).as_component(), + |_, _| true, + root_props, + "root", + )), "app", ); diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 54df717a1..fa1e18fc4 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -10,7 +10,7 @@ use crate::{ webview::WebviewInstance, }; use crossbeam_channel::Receiver; -use dioxus_core::{ComponentFunction, CrossPlatformConfig, ElementId}; +use dioxus_core::{CrossPlatformConfig, ElementId}; use dioxus_html::{ native_bind::NativeFileEngine, FileEngine, HasFileData, HasFormData, HtmlEvent, PlatformEventData, @@ -29,14 +29,10 @@ use tao::{ }; /// The single top-level object that manages all the running windows, assets, shortcuts, etc -pub(crate) struct App< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: '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>>, + pub(crate) dioxus_config: Cell>>, pub(crate) cfg: Cell>, // Stuff we need mutable access to @@ -49,8 +45,6 @@ pub(crate) struct App< /// /// This includes stuff like the event handlers, shortcuts, etc as well as ways to modify *other* windows pub(crate) shared: Rc, - - phantom: PhantomData, } /// A bundle of state shared between all the windows, providing a way for us to communicate with running webview. @@ -65,15 +59,10 @@ pub struct SharedContext { pub(crate) target: EventLoopWindowTarget, } -impl< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - > App -{ +impl App { pub fn new( cfg: Config, - dioxus_config: CrossPlatformConfig, + dioxus_config: CrossPlatformConfig, ) -> (EventLoop, Self) { let event_loop = EventLoopBuilder::::with_user_event().build(); @@ -92,7 +81,6 @@ impl< proxy: event_loop.create_proxy(), target: event_loop.clone(), }), - phantom: PhantomData, }; // Set the event converter diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 3d8d7e23e..3019d167e 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -10,12 +10,8 @@ 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< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, ->( - dioxus_cfg: CrossPlatformConfig, +pub fn launch_with_props_blocking( + dioxus_cfg: CrossPlatformConfig, desktop_config: Config, ) { let (event_loop, mut app) = App::new(desktop_config, dioxus_cfg); @@ -60,10 +56,7 @@ pub struct DesktopPlatform; impl PlatformBuilder for DesktopPlatform { type Config = Config; - fn launch, Phantom: 'static>( - config: dioxus_core::CrossPlatformConfig, - 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() diff --git a/packages/dioxus/src/launch.rs b/packages/dioxus/src/launch.rs index 3ebc271e6..9b1647edf 100644 --- a/packages/dioxus/src/launch.rs +++ b/packages/dioxus/src/launch.rs @@ -4,29 +4,19 @@ use std::any::Any; use crate::prelude::*; use dioxus_core::prelude::*; -use dioxus_core::ComponentFunction; use dioxus_core::{BoxedContext, CrossPlatformConfig, PlatformBuilder}; /// A builder for a fullstack app. -pub struct LaunchBuilder< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - Platform: PlatformBuilder = CurrentPlatform, -> { - cross_platform_config: CrossPlatformConfig, +pub struct LaunchBuilder = CurrentPlatform> +{ + cross_platform_config: CrossPlatformConfig, platform_config: Option<>::Config>, } // Default platform builder -impl< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - > LaunchBuilder -{ +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(component: Component) -> Self + pub fn new(component: impl ComponentFn) -> Self where Props: Default, { @@ -41,13 +31,7 @@ impl< } } -impl< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - Platform: PlatformBuilder, - > LaunchBuilder -{ +impl> LaunchBuilder { /// Pass some props to your application. pub fn props(mut self, props: Props) -> Self { self.cross_platform_config.props = props; @@ -84,12 +68,7 @@ impl< } #[cfg(feature = "web")] -impl< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - > LaunchBuilder -{ +impl LaunchBuilder { /// Launch your web application. pub fn launch_web(self) { dioxus_web::WebPlatform::launch( @@ -100,12 +79,7 @@ impl< } #[cfg(feature = "desktop")] -impl< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, - > LaunchBuilder -{ +impl LaunchBuilder { /// Launch your desktop application. pub fn launch_desktop(self) { dioxus_desktop::DesktopPlatform::launch( @@ -123,42 +97,27 @@ type CurrentPlatform = dioxus_web::WebPlatform; type CurrentPlatform = (); /// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, ->( - component: Component, -) where - Props: Default, +pub fn launch(component: impl ComponentFn) +where + Props: Default + Clone + 'static, { LaunchBuilder::new(component).launch() } #[cfg(feature = "web")] /// Launch your web application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch_web< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, ->( - component: Component, -) where - Props: Default, +pub fn launch_web(component: impl ComponentFn) +where + Props: Default + Clone + 'static, { LaunchBuilder::new(component).launch_web() } #[cfg(feature = "desktop")] /// Launch your desktop application without any additional configuration. See [`LaunchBuilder`] for more options. -pub fn launch_desktop< - Component: ComponentFunction, - Props: Clone + 'static, - Phantom: 'static, ->( - component: Component, -) where - Props: Default, +pub fn launch_desktop(component: impl ComponentFn) +where + Props: Default + Clone + 'static, { LaunchBuilder::new(component).launch_desktop() }