diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml index 1898d8bd1..00659dcdc 100644 --- a/packages/core-macro/Cargo.toml +++ b/packages/core-macro/Cargo.toml @@ -17,7 +17,6 @@ proc-macro2 = { version = "1.0" } quote = "1.0" syn = { version = "2.0", features = ["full", "extra-traits"] } dioxus-rsx = { workspace = true } -dioxus-core = { workspace = true } constcat = "0.3.0" prettyplease = "0.2.15" diff --git a/packages/core-macro/src/component_body/mod.rs b/packages/core-macro/src/component_body/mod.rs index 91e66c42c..42e54610d 100644 --- a/packages/core-macro/src/component_body/mod.rs +++ b/packages/core-macro/src/component_body/mod.rs @@ -147,9 +147,7 @@ pub mod utils; pub use utils::DeserializerArgs; pub use utils::DeserializerOutput; -pub use utils::TypeHelper; -use dioxus_core::{Element, Scope}; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::*; @@ -167,12 +165,6 @@ pub struct ComponentBody { /// Keep this in mind when creating deserializers, because you often might want to ignore it. /// That might be annoying, but it would be bad design for this kind of struct to not be parsable from itself. pub item_fn: ItemFn, - /// Parsing tries to ensure that this argument will be a [`Scope`]. - /// **However, macros have limitations that prevent this from always working, - /// so don't take this for granted!** - pub cx_arg: FnArg, - /// The pattern (name) and type of the context argument. - pub cx_pat_type: PatType, /// If the function has any arguments other than the context. pub has_extra_args: bool, } @@ -192,31 +184,8 @@ impl ComponentBody { impl Parse for ComponentBody { fn parse(input: ParseStream) -> Result { let item_fn: ItemFn = input.parse()?; - let scope_type_path = Scope::get_path_string(); - let (cx_arg, cx_pat_type) = if let Some(first_arg) = item_fn.sig.inputs.first() { - let incorrect_first_arg_err = Err(Error::new( - first_arg.span(), - format!("First argument must be a <{}>", scope_type_path), - )); - - match first_arg.to_owned() { - FnArg::Receiver(_) => { - return incorrect_first_arg_err; - } - FnArg::Typed(f) => (first_arg.to_owned(), f), - } - } else { - return Err(Error::new( - item_fn.sig.ident.span(), // Or maybe just item_f.sig.span()? - format!( - "Must have at least one argument that's a <{}>", - scope_type_path - ), - )); - }; - - let element_type_path = Element::get_path_string(); + let element_type_path = "::dioxus::core::Element"; if item_fn.sig.output == ReturnType::Default { return Err(Error::new( @@ -229,8 +198,6 @@ impl Parse for ComponentBody { Ok(Self { item_fn, - cx_arg, - cx_pat_type, has_extra_args, }) } diff --git a/packages/core-macro/src/component_body/utils.rs b/packages/core-macro/src/component_body/utils.rs index 05a2869d7..1a410eb28 100644 --- a/packages/core-macro/src/component_body/utils.rs +++ b/packages/core-macro/src/component_body/utils.rs @@ -1,5 +1,4 @@ use crate::component_body::ComponentBody; -use dioxus_core::{Element, Scope}; use quote::ToTokens; use syn::{parse_quote, Path}; @@ -36,22 +35,3 @@ where #[allow(unused_qualifications)] fn to_output(&self, component_body: &ComponentBody) -> syn::Result; } - -pub trait TypeHelper { - fn get_path() -> Path; - fn get_path_string() -> String { - Self::get_path().to_token_stream().to_string() - } -} - -impl<'a> TypeHelper for Scope<'a> { - fn get_path() -> Path { - parse_quote!(::dioxus::core::Scope) - } -} - -impl<'a> TypeHelper for Element<'a> { - fn get_path() -> Path { - parse_quote!(::dioxus::core::Element) - } -} diff --git a/packages/core-macro/src/component_body_deserializers/component.rs b/packages/core-macro/src/component_body_deserializers/component.rs index 994e51709..429a9f440 100644 --- a/packages/core-macro/src/component_body_deserializers/component.rs +++ b/packages/core-macro/src/component_body_deserializers/component.rs @@ -16,7 +16,7 @@ To ignore this check, pass the \"", const INNER_FN_NAME: &str = "__dx_inner_comp"; -fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn { +fn get_out_comp_fn(orig_comp_fn: &ItemFn) -> ItemFn { let inner_comp_ident = Ident::new(INNER_FN_NAME, orig_comp_fn.sig.ident.span()); let inner_comp_fn = ItemFn { @@ -34,7 +34,7 @@ fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn { #[allow(clippy::inline_always)] #[inline(always)] #inner_comp_fn - #inner_comp_ident (#cx_pat) + #inner_comp_ident(__props) } }, ..orig_comp_fn.clone() @@ -88,14 +88,9 @@ impl DeserializerArgs for ComponentDeserializerArgs impl ComponentDeserializerArgs { fn deserialize_no_props(component_body: &ComponentBody) -> ComponentDeserializerOutput { - let ComponentBody { - item_fn, - cx_pat_type, - .. - } = component_body; - let cx_pat = &cx_pat_type.pat; + let ComponentBody { item_fn, .. } = component_body; - let comp_fn = get_out_comp_fn(item_fn, cx_pat); + let comp_fn = get_out_comp_fn(item_fn); ComponentDeserializerOutput { comp_fn, @@ -106,12 +101,7 @@ impl ComponentDeserializerArgs { fn deserialize_with_props( component_body: &ComponentBody, ) -> Result { - let ComponentBody { - item_fn, - cx_pat_type, - .. - } = component_body; - let cx_pat = &cx_pat_type.pat; + let ComponentBody { item_fn, .. } = component_body; let comp_parsed = match parse2::(quote!(#item_fn)) { Ok(comp_body) => comp_body, @@ -130,7 +120,7 @@ impl ComponentDeserializerArgs { let props_struct = inlined_props_output.props_struct; let props_fn = inlined_props_output.comp_fn; - let comp_fn = get_out_comp_fn(&props_fn, cx_pat); + let comp_fn = get_out_comp_fn(&props_fn); Ok(ComponentDeserializerOutput { comp_fn, diff --git a/packages/core-macro/src/component_body_deserializers/inline_props.rs b/packages/core-macro/src/component_body_deserializers/inline_props.rs index d51132623..35eca3f57 100644 --- a/packages/core-macro/src/component_body_deserializers/inline_props.rs +++ b/packages/core-macro/src/component_body_deserializers/inline_props.rs @@ -225,11 +225,7 @@ fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec { } fn get_function(component_body: &ComponentBody) -> ItemFn { - let ComponentBody { - item_fn, - cx_pat_type, - .. - } = component_body; + let ComponentBody { item_fn, .. } = component_body; let ItemFn { attrs: fn_attrs, vis, @@ -246,7 +242,6 @@ fn get_function(component_body: &ComponentBody) -> ItemFn { } = sig; let Generics { where_clause, .. } = generics; - let cx_pat = &cx_pat_type.pat; let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span()); // Skip first arg since that's the context @@ -298,10 +293,10 @@ fn get_function(component_body: &ComponentBody) -> ItemFn { parse_quote! { #(#fn_attrs)* #(#props_docs)* - #asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output + #asyncness #vis fn #fn_ident #fn_generics (__props: #struct_ident #generics_no_bounds) #fn_output #where_clause { - let #struct_ident { #(#struct_field_names),* } = &#cx_pat.props; + let #struct_ident { #(#struct_field_names),* } = &__props; #fn_block } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index d8e97981d..b0c468c34 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -155,7 +155,7 @@ impl VirtualDom { } Element { .. } => { #[cfg(not(debug_assertions))] - let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[]); + let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[], to); #[cfg(debug_assertions)] let id = self.write_element_root( node, diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 973f164d1..c36cd4707 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -37,11 +37,22 @@ impl Debug for ErrorBoundaryInner { } } +/// A trait for any type that can be downcast to a concrete type and implements Debug. This is automatically implemented for all types that implement Any + Debug. +pub trait AnyDebug: Any + Debug { + fn as_any(&self) -> &dyn Any; +} + +impl AnyDebug for T { + fn as_any(&self) -> &dyn Any { + self + } +} + #[derive(Debug)] /// An instance of an error captured by a descendant component. pub struct CapturedError { /// The error captured by the error boundary - pub error: Box, + pub error: Box, /// The backtrace of the error pub backtrace: Backtrace, @@ -65,8 +76,7 @@ impl CapturedError { /// Downcast the error type into a concrete error type pub fn downcast(&self) -> Option<&T> { if TypeId::of::() == self.error.type_id() { - let raw = self.error.as_ref() as *const _ as *const T; - Some(unsafe { &*raw }) + self.error.as_any().downcast_ref::() } else { None } @@ -100,14 +110,9 @@ impl ErrorBoundary { } /// Push an error into this Error Boundary - pub fn insert_error( - &self, - scope: ScopeId, - error: Box, - backtrace: Backtrace, - ) { + pub fn insert_error(&self, scope: ScopeId, error: impl Debug + 'static, backtrace: Backtrace) { self.inner.error.replace(Some(CapturedError { - error, + error: Box::new(error), scope, backtrace, })); diff --git a/packages/core/src/global_context.rs b/packages/core/src/global_context.rs index 768348cea..bca2f22d0 100644 --- a/packages/core/src/global_context.rs +++ b/packages/core/src/global_context.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use futures_util::Future; use crate::{ @@ -113,3 +115,21 @@ pub fn parent_scope() -> Option { pub fn needs_update() { with_current_scope(|cx| cx.needs_update()); } + +/// Schedule an update for the current component +/// +/// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime. +/// +/// You should prefer [`schedule_update_any`] if you need to update multiple components. +pub fn schedule_update() -> Arc { + with_current_scope(|cx| cx.schedule_update()).expect("to be in a dioxus runtime") +} + +/// Schedule an update for any component given its [`ScopeId`]. +/// +/// A component's [`ScopeId`] can be obtained from the [`current_scope_id`] method. +/// +/// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime. +pub fn schedule_update_any() -> Arc { + with_current_scope(|cx| cx.schedule_update_any()).expect("to be in a dioxus runtime") +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 09c17e510..7f4466dd8 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -74,10 +74,11 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, generation, once, vdom_is_rendering, AnyValue, Attribute, AttributeValue, - CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, - Mutation, MutationsVec, Properties, RenderReturn, ScopeId, Task, Template, TemplateAttribute, - TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, WriteMutations, + fc_to_builder, generation, once, schedule_update, schedule_update_any, vdom_is_rendering, + AnyValue, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element, ElementId, + Event, Fragment, IntoDynNode, Mutation, MutationsVec, Properties, RenderReturn, ScopeId, Task, + Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, + WriteMutations, }; /// The purpose of this module is to alleviate imports of many common types @@ -87,9 +88,9 @@ pub mod prelude { pub use crate::innerlude::{ consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation, has_context, needs_update, once, parent_scope, provide_context, provide_root_context, - push_future, remove_future, spawn, spawn_forever, suspend, use_error_boundary, AnyValue, - Component, Element, ErrorBoundary, Event, EventHandler, Fragment, IntoAttributeValue, - IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, Task, Template, TemplateAttribute, - TemplateNode, Throw, VNode, VirtualDom, + push_future, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, + suspend, use_error_boundary, AnyValue, Component, Element, ErrorBoundary, Event, + EventHandler, Fragment, IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard, + ScopeId, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, }; } diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 54182ff92..32e5faef0 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -9,6 +9,7 @@ use std::{ cell::{Cell, RefCell}, future::Future, rc::Rc, + sync::Arc, }; /// A component's state separate from its props. @@ -69,6 +70,26 @@ impl ScopeContext { .expect("Scheduler to exist if scope exists"); } + /// Create a subscription that schedules a future render for the reference component + /// + /// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`] + pub fn schedule_update(&self) -> Arc { + let (chan, id) = (self.tasks.sender.clone(), self.id); + Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id)))) + } + + /// Schedule an update for any component given its [`ScopeId`]. + /// + /// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method. + /// + /// This method should be used when you want to schedule an update for a component + pub fn schedule_update_any(&self) -> Arc { + let chan = self.tasks.sender.clone(); + Arc::new(move |id| { + chan.unbounded_send(SchedulerMsg::Immediate(id)).unwrap(); + }) + } + /// Return any context of type T if it exists on this scope pub fn has_context(&self) -> Option { self.shared_contexts @@ -355,4 +376,11 @@ impl ScopeId { pub fn needs_update(self) { with_scope(self, |cx| cx.needs_update()); } + + /// Create a subscription that schedules a future render for the reference component. Unlike [`Self::needs_update`], this function will work outside of the dioxus runtime. + /// + /// ## Notice: you should prefer using [`dioxus_core::schedule_update_any`] and [`Self::scope_id`] + pub fn schedule_update(&self) -> Arc { + with_scope(*self, |cx| cx.schedule_update()).expect("to be in a dioxus runtime") + } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 19142bad6..44383cef2 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -549,7 +549,9 @@ impl VirtualDom { pub fn rebuild(&mut self, to: &mut impl WriteMutations) { self.flush_templates(to); let _runtime = RuntimeGuard::new(self.runtime.clone()); - match self.run_scope(ScopeId::ROOT) { + let new_nodes = self.run_scope(ScopeId::ROOT); + self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone()); + match new_nodes { // Rebuilding implies we append the created elements to the root RenderReturn::Ready(node) => { let m = self.create_scope(ScopeId::ROOT, &node, to); diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs index d0419e8c1..e55a34836 100644 --- a/packages/desktop/src/eval.rs +++ b/packages/desktop/src/eval.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use dioxus_core::prelude::consume_context; use dioxus_core::prelude::provide_context; +use dioxus_core::ScopeId; use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator}; use std::{cell::RefCell, rc::Rc}; @@ -9,13 +10,13 @@ use crate::{query::Query, DesktopContext}; /// Provides the DesktopEvalProvider through [`cx.provide_context`]. pub fn init_eval() { - let desktop_ctx = consume_context::().unwrap(); + let desktop_ctx = ScopeId::ROOT.consume_context::().unwrap(); let provider: Rc = Rc::new(DesktopEvalProvider { desktop_ctx }); - provide_context(provider); + ScopeId::ROOT.provide_context(provider); } -/// Reprents the desktop-target's provider of evaluators. -pub struct DesktopEvalProvider { +/// Represents the desktop-target's provider of evaluators. +pub(crate) struct DesktopEvalProvider { desktop_ctx: DesktopContext, } @@ -25,8 +26,8 @@ impl EvalProvider for DesktopEvalProvider { } } -/// Reprents a desktop-target's JavaScript evaluator. -pub struct DesktopEvaluator { +/// Represents a desktop-target's JavaScript evaluator. +pub(crate) struct DesktopEvaluator { query: Rc>>, } diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 8178d4d3d..68c195d2f 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -55,42 +55,42 @@ macro_rules! to_owned { }; } -pub mod computed; +// pub mod computed; -mod use_on_destroy; -pub use use_on_destroy::*; +// mod use_on_destroy; +// pub use use_on_destroy::*; -mod use_const; -pub use use_const::*; +// mod use_const; +// pub use use_const::*; -mod use_context; -pub use use_context::*; +// mod use_context; +// pub use use_context::*; -mod use_state; -pub use use_state::{use_state, UseState}; +// mod use_state; +// pub use use_state::{use_state, UseState}; -mod use_ref; -pub use use_ref::*; +// mod use_ref; +// pub use use_ref::*; -mod use_shared_state; -pub use use_shared_state::*; +// mod use_shared_state; +// pub use use_shared_state::*; -mod use_coroutine; -pub use use_coroutine::*; +// mod use_coroutine; +// pub use use_coroutine::*; -mod use_future; -pub use use_future::*; +// mod use_future; +// pub use use_future::*; -mod use_effect; -pub use use_effect::*; +// mod use_effect; +// pub use use_effect::*; -mod use_callback; -pub use use_callback::*; +// mod use_callback; +// pub use use_callback::*; -mod use_memo; -pub use use_memo::*; +// mod use_memo; +// pub use use_memo::*; -mod use_on_create; -pub use use_on_create::*; -mod use_root_context; -pub use use_root_context::*; +// mod use_on_create; +// pub use use_on_create::*; +// mod use_root_context; +// pub use use_root_context::*; diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index 38cb7c853..97cc15673 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -68,7 +68,7 @@ impl ToTokens for ElementAttrNamed { let value = &self.attr.value; let value = quote! { #value }; quote! { - __cx.attr( + ::dioxus::core::Attribute::new( #attribute, #value, #ns, @@ -79,7 +79,7 @@ impl ToTokens for ElementAttrNamed { ElementAttrValue::EventTokens(tokens) => match &self.attr.name { ElementAttrName::BuiltIn(name) => { quote! { - dioxus_elements::events::#name(__cx, #tokens) + dioxus_elements::events::#name(#tokens) } } ElementAttrName::Custom(_) => todo!(), diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 6487e5ab7..c947e71da 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -189,11 +189,11 @@ impl ToTokens for Component { }; tokens.append_all(quote! { - __cx.component( + ::dioxus::core::DynamicNode::Component(::dioxus::core::VComponent::new( #gen_name, #builder, #fn_name - ) + )) }) } } @@ -217,10 +217,10 @@ impl ToTokens for ContentField { match self { ContentField::ManExpr(e) => e.to_tokens(tokens), ContentField::Formatted(s) => tokens.append_all(quote! { - __cx.raw_text(#s) + #s.to_string() }), ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { - __cx.event_handler(#e) + ::dioxus::core::Attribute::EventHandler(EventHandler::new(#e)) }), } } diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 86d530d33..3e14c41f8 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -184,11 +184,11 @@ impl ToTokens for Element { .filter(|f| !matches!(f.attr.value, ElementAttrValue::EventTokens { .. })); tokens.append_all(quote! { - __cx.element( + ::dioxus::core::Element::new( #name, - __cx.bump().alloc([ #(#listeners),* ]), - __cx.bump().alloc([ #(#attr),* ]), - __cx.bump().alloc([ #(#children),* ]), + vec![ #(#listeners),* ], + vec![ #(#attr),* ], + vec![ #(#children),* ], #key, ) }); diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index f1464f0f2..ee8d1d5ed 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -69,7 +69,7 @@ impl CallBody { &self, template: Option, location: &'static str, - ) -> Option> { + ) -> Option