Remove phantom markers and just default to Rc<dyn Fn(props) -> Element> where it makes sense

This commit is contained in:
Jonathan Kelley 2024-01-16 15:07:22 -08:00
parent 397015df31
commit c94af9538b
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
9 changed files with 108 additions and 234 deletions

View file

@ -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<dyn AnyProps>;
}
pub(crate) struct VProps<P: 'static, F: ComponentFunction<Phantom, Props = P>, Phantom: 'static> {
pub render_fn: F,
pub(crate) struct VProps<P: 'static> {
pub render_fn: Component<P>,
pub memo: fn(&P, &P) -> bool,
pub props: P,
pub name: &'static str,
phantom: PhantomData<Phantom>,
}
impl<P: 'static, F: ComponentFunction<Phantom, Props = P>, Phantom: 'static> VProps<P, F, Phantom> {
impl<P: 'static> VProps<P> {
pub(crate) fn new(
render_fn: F,
render_fn: Component<P>,
memo: fn(&P, &P) -> bool,
props: P,
name: &'static str,
@ -58,14 +57,11 @@ impl<P: 'static, F: ComponentFunction<Phantom, Props = P>, Phantom: 'static> VPr
memo,
props,
name,
phantom: PhantomData,
}
}
}
impl<P: Clone + 'static, F: ComponentFunction<Phantom, Props = P>, Phantom> AnyProps
for VProps<P, F, Phantom>
{
impl<P: Clone + 'static> AnyProps for VProps<P> {
fn memoize(&self, other: &dyn Any) -> bool {
match other.downcast_ref::<P>() {
Some(other) => (self.memo)(&self.props, other),
@ -80,7 +76,7 @@ impl<P: Clone + 'static, F: ComponentFunction<Phantom, Props = P>, 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<P: Clone + 'static, F: ComponentFunction<Phantom, Props = P>, Phantom> AnyP
memo: self.memo,
props: self.props.clone(),
name: self.name,
phantom: PhantomData,
})
}
}

View file

@ -70,12 +70,12 @@ pub(crate) mod innerlude {
/// Example {}
/// )
/// ```
pub type Component<P = ()> = fn(P) -> Element;
pub type Component<P = ()> = std::rc::Rc<dyn Fn(P) -> 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,
};
}

View file

@ -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<Props<'_>>) -> Element;
/// ```
pub fn new<F: ComponentFunction<P> + 'static, P: 'static>(
component: F,
props: F::Props,
fn_name: &'static str,
) -> Self
pub fn new<P, M>(component: impl ComponentFn<P, M>, 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::<F>();
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, <P as Properties>::memoize, props, fn_name);
VComponent {
name: fn_name,
render_fn: render_fn_id,
props: BoxedAnyProps::new(vcomp),
render_fn,
}
}

View file

@ -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<dyn ClonableAny>);
@ -40,33 +40,26 @@ impl<T: Any + Clone> ClonableAny for T {
}
/// The platform-independent part of the config needed to launch an application.
pub struct CrossPlatformConfig<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> {
pub struct CrossPlatformConfig<Props: Clone + 'static> {
/// The root component function.
pub component: Component,
pub component: Component<Props>,
/// The props for the root component.
pub props: Props,
/// The contexts to provide to the root component.
pub root_contexts: Vec<BoxedContext>,
_phantom: PhantomData<Phantom>,
}
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> CrossPlatformConfig<Component, Props, Phantom>
{
impl<Props: Clone + 'static> CrossPlatformConfig<Props> {
/// Create a new cross-platform config.
pub fn new(component: Component, props: Props, root_contexts: Vec<BoxedContext>) -> Self {
pub fn new<M>(
component: impl ComponentFn<Props, M>,
props: Props,
root_contexts: Vec<BoxedContext>,
) -> Self {
Self {
component,
component: ComponentFn::as_component(std::rc::Rc::new(component)),
props,
root_contexts,
_phantom: PhantomData,
}
}
@ -88,19 +81,13 @@ pub trait PlatformBuilder<Props: Clone + 'static> {
type Config: Default;
/// Launch the app.
fn launch<Component: ComponentFunction<Phantom, Props = Props>, Phantom: 'static>(
config: CrossPlatformConfig<Component, Props, Phantom>,
platform_config: Self::Config,
);
fn launch(config: CrossPlatformConfig<Props>, platform_config: Self::Config);
}
impl<Props: Clone + 'static> PlatformBuilder<Props> for () {
type Config = ();
fn launch<Component: ComponentFunction<Phantom, Props = Props>, Phantom: 'static>(
_: CrossPlatformConfig<Component, Props, Phantom>,
_: Self::Config,
) {
fn launch(_: CrossPlatformConfig<Props>, _: Self::Config) {
panic!("No platform is currently enabled. Please enable a platform feature for the dioxus crate.");
}
}

View file

@ -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<F: ComponentFunction<P>, P>(
_: F,
) -> <<F as ComponentFunction<P>>::Props as Properties>::Builder
pub fn fc_to_builder<P, M>(_: impl ComponentFn<P, M>) -> <P as Properties>::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<P>: 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<Props, Marker> {
/// Convert the component to a function that takes props and returns an element.
fn as_component(self: Rc<Self>) -> Component<Props>;
}
impl<T: 'static, F: Fn(T) -> 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<P> ComponentFn<P, ()> for Component<P> {
fn as_component(self: Rc<Self>) -> Component<P> {
self.as_ref().clone()
}
}
#[doc(hidden)]
pub struct ZeroElementMarker;
impl<F: Fn() -> Element + Clone + 'static> ComponentFunction<ZeroElementMarker> for F {
type Props = ();
/// Accept any callbacks that take props
impl<F: Fn(P) -> Element + 'static, P> ComponentFn<P, ()> for F {
fn as_component(self: Rc<Self>) -> Component<P> {
self
}
}
fn call(&self, _: ()) -> Element {
self()
/// Accept any callbacks that take no props
pub struct EmptyMarker;
impl<F: Fn() -> Element + 'static> ComponentFn<(), EmptyMarker> for F {
fn as_component(self: Rc<Self>) -> Rc<dyn Fn(()) -> 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<dyn ComponentFn<(), ()>> = Rc::new(test) as Rc<dyn ComponentFn<_, _>>;
let callable2: Rc<dyn ComponentFn<(), EmptyMarker>> =
Rc::new(test2) as Rc<dyn ComponentFn<_, _>>;
}

View file

@ -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<Phantom, Props = P>,
P: Clone + 'static,
Phantom: 'static,
>(
root: F,
pub fn new_with_props<P: Clone + 'static, M>(
root: impl ComponentFn<P, M>,
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",
);

View file

@ -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<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> {
pub(crate) struct App<Props: Clone + 'static> {
// 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<Component, Props, Phantom>>>,
pub(crate) dioxus_config: Cell<Option<CrossPlatformConfig<Props>>>,
pub(crate) cfg: Cell<Option<Config>>,
// 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<SharedContext>,
phantom: PhantomData<Phantom>,
}
/// 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<UserWindowEvent>,
}
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> App<Component, Props, Phantom>
{
impl<Props: Clone + 'static> App<Props> {
pub fn new(
cfg: Config,
dioxus_config: CrossPlatformConfig<Component, Props, Phantom>,
dioxus_config: CrossPlatformConfig<Props>,
) -> (EventLoop<UserWindowEvent>, Self) {
let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
@ -92,7 +81,6 @@ impl<
proxy: event_loop.create_proxy(),
target: event_loop.clone(),
}),
phantom: PhantomData,
};
// Set the event converter

View file

@ -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<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
>(
dioxus_cfg: CrossPlatformConfig<Component, Props, Phantom>,
pub fn launch_with_props_blocking<Props: Clone + 'static>(
dioxus_cfg: CrossPlatformConfig<Props>,
desktop_config: Config,
) {
let (event_loop, mut app) = App::new(desktop_config, dioxus_cfg);
@ -60,10 +56,7 @@ pub struct DesktopPlatform;
impl<Props: Clone + 'static> PlatformBuilder<Props> for DesktopPlatform {
type Config = Config;
fn launch<Component: ComponentFunction<Phantom, Props = Props>, Phantom: 'static>(
config: dioxus_core::CrossPlatformConfig<Component, Props, Phantom>,
platform_config: Self::Config,
) {
fn launch(config: dioxus_core::CrossPlatformConfig<Props>, platform_config: Self::Config) {
#[cfg(feature = "tokio")]
tokio::runtime::Builder::new_multi_thread()
.enable_all()

View file

@ -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<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
Platform: PlatformBuilder<Props> = CurrentPlatform,
> {
cross_platform_config: CrossPlatformConfig<Component, Props, Phantom>,
pub struct LaunchBuilder<Props: Clone + 'static, Platform: PlatformBuilder<Props> = CurrentPlatform>
{
cross_platform_config: CrossPlatformConfig<Props>,
platform_config: Option<<Platform as PlatformBuilder<Props>>::Config>,
}
// Default platform builder
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> LaunchBuilder<Component, Props, Phantom>
{
impl<Props: Clone + 'static> LaunchBuilder<Props> {
/// 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<M>(component: impl ComponentFn<Props, M>) -> Self
where
Props: Default,
{
@ -41,13 +31,7 @@ impl<
}
}
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
Platform: PlatformBuilder<Props>,
> LaunchBuilder<Component, Props, Phantom, Platform>
{
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;
@ -84,12 +68,7 @@ impl<
}
#[cfg(feature = "web")]
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> LaunchBuilder<Component, Props, Phantom, dioxus_web::WebPlatform>
{
impl<Props: Clone + 'static> LaunchBuilder<Props, dioxus_web::WebPlatform> {
/// Launch your web application.
pub fn launch_web(self) {
dioxus_web::WebPlatform::launch(
@ -100,12 +79,7 @@ impl<
}
#[cfg(feature = "desktop")]
impl<
Component: ComponentFunction<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
> LaunchBuilder<Component, Props, Phantom, dioxus_desktop::DesktopPlatform>
{
impl<Props: Clone + 'static> LaunchBuilder<Props, dioxus_desktop::DesktopPlatform> {
/// 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<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
>(
component: Component,
) where
Props: Default,
pub fn launch<Props, Marker>(component: impl ComponentFn<Props, Marker>)
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<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
>(
component: Component,
) where
Props: Default,
pub fn launch_web<Props, Marker>(component: impl ComponentFn<Props, Marker>)
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<Phantom, Props = Props>,
Props: Clone + 'static,
Phantom: 'static,
>(
component: Component,
) where
Props: Default,
pub fn launch_desktop<Props, Marker>(component: impl ComponentFn<Props, Marker>)
where
Props: Default + Clone + 'static,
{
LaunchBuilder::new(component).launch_desktop()
}