diff --git a/.vscode/spellright.dict b/.vscode/spellright.dict index d0a747992..9e666a66f 100644 --- a/.vscode/spellright.dict +++ b/.vscode/spellright.dict @@ -26,3 +26,4 @@ rei RefMut diffed datafetching +partialeq diff --git a/notes/CHANGELOG.md b/notes/CHANGELOG.md index 2dbe36de1..9be0d2427 100644 --- a/notes/CHANGELOG.md +++ b/notes/CHANGELOG.md @@ -1,3 +1,8 @@ +# Project: Web-View 🤲 🍨 +> Proof of concept: stream render edits from server to client +- [x] Prove that the diffing and patching framework can support patch streaming + + # Project: Live-View 🤲 🍨 > Combine the server and client into a single file :) diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index 37da3855c..e7c58aecf 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -287,3 +287,71 @@ On the actual diffing level, we're using the diffing algorithm pulled from Dodri ## Patch Stream One of the most important parts of Dioxus is the ability to stream patches from server to client. However, this inherently has challenges where raw VNodes attach listeners to themselves, and are therefore not serializable. + + +### How do properties work? + +How should properties passing work? Should we directly call the child? Should we box the props? Should we replace the pops inside the box? + +Here's (generally) my head is at: + +Components need to store their props on them if they want to be updated remotely. These props *can* be updated after the fact. + +Perf concerns: +unnecessary function runs + - list-y components + - hook calls? + - making vnodes? + +Does any of this matter? +Should we just run any component we see, immediately and imperatively? That will cause checks throughout the whole tree, no matter where the update occurred + + +https://calendar.perfplanet.com/2013/diff/ + +Here's how react does it: + +Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design: + +```rust +static APP: FC<()> = |ctx, props| { + let title = use_context(Title); + ctx.view(html!{ +
+

"{title}"

+ // VComponent::new(|| (FC, PropsForFc)) -> needs a context to immediately update the component's props imperatively? store the props in a box on bump? store the props on the child? + // if props didnt change, then let the refernece stay invalid?.... no, cant do that, bump gets reset + // immediately update props on the child component if it can be found? -> interesting, feels wrong, but faster, at the very least. + // can box on bump for the time being (fast enough), and then move it over? during the comparison phase? props only need to matter + // cant downcast (can with transmute, but yikes) + // how does chain borrowing work? a -> b -> c -> d + // if b gets marked as dirty, then c and d are invalidated (semantically, UB, but not *bad* UB, just data races) + // make props static? -> easy to move, gross to use + // + // treat like a context selector? + // use_props::

(2) + // child_props: Map> + // vs children: BTreeSet -> to get nth +

+ }) +}; +static HEAVY_LIST: FC<()> = |ctx, props| { + ctx.view({ + {0.100.map(i => )} + }) +}; +``` + +An update to the use_context subscription will mark the node as dirty. The node then is forced to re-analyze HeavyList, even though HeavyList did not change. We should automatically implement this suppression knowing that props are immutable and can be partialeq. + +## FC Layout + +The FC layout was altered to make life easier for us inside the VirtualDom. The "view" function returns an unbounded DomTree object. Calling the "view" function is unsafe under the hood, but prevents lifetimes from leaking out of the function call. Plus, it's easier to write. Because there are no lifetimes on the output (occur purely under the hood), we can escape needing to annotate them. + +```rust +fn component(ctx: Context, props: &Props) -> DomTree { + +} +``` + +The DomTree object purely represents a viewable "key". It also forces components to use the "view" function as there is no other way to generate the DomTree object. Because the DomTree is a required type of FC, we can guarantee the same usage and flow patterns for all components. diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index f9634b375..dbbd5818a 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -34,5 +34,5 @@ longest-increasing-subsequence = "0.1.0" serde = { version = "1.0.123", features = ["derive"] } log = "0.4.14" pretty_env_logger = "0.4.0" -ouroboros = "0.8.0" +# ouroboros = "0.8.0" # hashbrown = { version = "0.9.1", features = ["bumpalo"] } diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index 5ed5b1fe1..f31085f52 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -3,17 +3,69 @@ use std::marker::PhantomData; -use dioxus_core::prelude::VNode; +use bumpalo::Bump; +use dioxus_core::prelude::{DomTree, VNode}; fn main() {} -struct Context2<'a> { - _p: PhantomData<&'a ()>, +struct Context2<'a, P> { + props: &'a P, // _p: PhantomData<&'a ()>, +} +impl<'a, P> Context2<'a, P> { + fn view(&self, f: impl FnOnce(&'a Bump) -> VNode<'a>) -> DTree { + DTree {} + } + + pub fn use_hook<'scope, InternalHookState: 'static, Output: 'a>( + &'scope self, + initializer: impl FnOnce() -> InternalHookState, + runner: impl FnOnce(&'a mut InternalHookState) -> Output, + cleanup: impl FnOnce(InternalHookState), + ) -> Output { + todo!() + } } -type FC2<'a, 'b, 'c: 'a + 'b, P> = fn(Context2<'a>, &'b P) -> VNode<'c>; +struct DTree; +type FC2<'a, T: 'a> = fn(&'a Context2) -> DTree; -static Example: FC2<()> = |ctx, props| { +struct Props { + c: String, +} + +static Example: FC2 = |ctx| { + let val = use_state(&ctx, || String::from("asd")); + + ctx.view(move |b| { + let g: VNode<'_> = virtual_child(b, Props2 { a: val }, alt_child); + // + dioxus_core::nodebuilder::div(b) + .child(dioxus_core::nodebuilder::text(ctx.props.c.as_str())) + .child(g) + .finish() + }) +}; + +fn virtual_child<'a, T: 'a>(bump: &'a Bump, props: T, f: FC2) -> VNode<'a> { + todo!() +} + +struct Props2<'a> { + a: &'a String, +} + +fn alt_child<'a>(ctx: &Context2<'a, Props2<'_>>) -> DomTree { + todo!() +} + +static CHILD: FC2 = |ctx| { // todo!() }; + +fn use_state<'a, 'c, P, T: 'static, F: FnOnce() -> T>( + ctx: &'a Context2<'a, P>, + initial_state_fn: F, +) -> (&'a T) { + todo!() +} diff --git a/packages/core/examples/props.rs b/packages/core/examples/props.rs new file mode 100644 index 000000000..4b95619e5 --- /dev/null +++ b/packages/core/examples/props.rs @@ -0,0 +1,32 @@ +use std::marker::PhantomData; + +fn main() {} + +trait Props<'parent> {} + +struct SomeProps<'p> { + text: &'p str, +} + +impl<'p> Props<'p> for SomeProps<'p> {} + +struct OutputNode<'a> { + _p: PhantomData<&'a ()>, +} + +// combine reference to self (borrowed from self) and referenfce to parent (borrowed from parent) +// borrow chain looks like 'p + 's -> 'p + 's -> 'p + 's +// always adding new lifetimes from self into the mix +// what does a "self" lifetime mean? +// a "god" gives us our data +// the god's lifetime is tied to Context, and the borrowed props object +// for the sake of simplicity, we just clobber lifetimes. +// user functions are just lies and we abuse lifetimes. +// everything is managed at runtime because that's how we make something ergonomc +// lifetime management in dioxus is just cheating around the rules +// our kind god manages lifetimes for us so we don't have to, thanks god +fn something<'s>(props: &'s SomeProps<'s>) -> OutputNode<'s> { + todo!() +} + +// type BC<'p, P: Props<'p>> = for<'a, 'b, 'c> fn(&'a P<'b>) -> OutputNode<'c>; diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 681946238..ce8f1a441 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -16,16 +16,18 @@ fn main() -> Result<(), ()> { Ok(()) } +#[derive(Debug)] struct Props { name: String, } -impl Properties for Props { - fn call(&self, ptr: *const ()) {} - // fn new() -> Self { - // todo!() - // } -} +// impl Properties for Props { +// fn call(&self, ptr: *const ()) {} + +// // fn new() -> Self { +// // todo!() +// // } +// } static Example: FC = |ctx, props| { ctx.view(html! { diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index a464b5fbd..dd7328c33 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -2,44 +2,47 @@ //! for components to be used within Nodes. //! +use std::fmt::Debug; + use crate::innerlude::*; /// The `Component` trait refers to any struct or funciton that can be used as a component /// We automatically implement Component for FC -pub trait Component { - type Props: Properties; - fn builder(&'static self) -> Self::Props; -} +// pub trait Component { +// type Props: Properties<'static>; +// fn builder(&'static self) -> Self::Props; +// } -// Auto implement component for a FC -// Calling the FC is the same as "rendering" it -impl Component for FC

{ - type Props = P; +// // Auto implement component for a FC +// // Calling the FC is the same as "rendering" it +// impl> Component for FC

{ +// type Props = P; - fn builder(&self) -> Self::Props { - todo!() - } -} +// fn builder(&self) -> Self::Props { +// todo!() +// } +// } /// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields. /// Components take a "properties" object -pub trait Properties // where -// Self: Sized, -{ - fn call(&self, ptr: *const ()) {} -} +// pub trait Properties<'a> +// where +// Self: Debug, +// { +// fn call(&self, ptr: *const ()) {} +// } -// Auto implement for no-prop components -impl Properties for () { - fn call(&self, ptr: *const ()) {} -} +// // Auto implement for no-prop components +// impl<'a> Properties<'a> for () { +// fn call(&self, ptr: *const ()) {} +// } #[cfg(test)] mod tests { use super::*; use crate::prelude::bumpalo::Bump; - fn test_static_fn<'a, P: Properties>(b: &'a Bump, r: FC

) -> VNode<'a> { + fn test_static_fn<'a, P>(b: &'a Bump, r: FC

) -> VNode<'a> { todo!() } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 4ea1dd1a5..1044c8a77 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -68,8 +68,12 @@ impl<'a> Context<'a> { /// ctx.view(lazy_tree) /// } ///``` - pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> { - lazy_nodes(self.bump) + pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> DomTree { + // pub fn view(self, lazy_nodes: impl for<'b> FnOnce(&'b Bump) -> VNode<'b> + 'a + 'p) -> DomTree { + // pub fn view<'p>(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a + 'p) -> DomTree { + // pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> { + let g = lazy_nodes(self.bump); + DomTree {} } pub fn callback(&self, f: impl Fn(()) + 'static) {} @@ -103,12 +107,12 @@ pub mod hooks { /// TODO: @jon, rework this so we dont have to use unsafe to make hooks and then return them /// use_hook provides a way to store data between renders for functional components. /// todo @jon: ensure the hook arena is stable with pin or is stable by default - pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>( + pub fn use_hook<'scope, InternalHookState: 'static, Output: 'a>( &'scope self, // The closure that builds the hook state initializer: impl FnOnce() -> InternalHookState, // The closure that takes the hookstate and returns some value - runner: impl FnOnce(&'internal mut InternalHookState) -> Output, + runner: impl FnOnce(&'a mut InternalHookState) -> Output, // The closure that cleans up whatever mess is left when the component gets torn down // TODO: add this to the "clean up" group for when the component is dropped cleanup: impl FnOnce(InternalHookState), @@ -152,7 +156,7 @@ pub mod hooks { - We don't expose the raw hook pointer outside of the scope of use_hook - The reference is tied to context, meaning it can only be used while ctx is around to free it */ - let borrowed_hook: &'internal mut _ = unsafe { raw_hook.as_mut().unwrap() }; + let borrowed_hook: &'a mut _ = unsafe { raw_hook.as_mut().unwrap() }; let internal_state = borrowed_hook.0.downcast_mut::().unwrap(); diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index 3b63294c3..153658920 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -3,7 +3,7 @@ //! //! Renderers don't actually need to own the virtual dom (it's up to the implementer). -use crate::prelude::{Properties, VirtualDom}; +use crate::prelude::VirtualDom; pub struct DebugRenderer { vdom: VirtualDom, diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index ab7e53b24..d75131301 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -40,8 +40,8 @@ mod use_state_def { /// } /// } /// ``` - pub fn use_state<'a, T: 'static, F: FnOnce() -> T + 'static>( - ctx: &'_ Context<'a>, + pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>( + ctx: &'c Context<'a>, initial_state_fn: F, ) -> (&'a T, &'a impl Fn(T)) { ctx.use_hook( @@ -93,7 +93,7 @@ mod use_ref_def { } } - fn modify(&self, modifier: impl FnOnce(&mut T)) { + pub fn modify(&self, modifier: impl FnOnce(&mut T)) { let mut val = self._current.borrow_mut(); let val_as_ref = val.deref_mut(); modifier(val_as_ref); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 0c572da82..19f7c8fab 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -87,7 +87,7 @@ pub mod builder { // types used internally that are important pub(crate) mod innerlude { - pub(crate) use crate::component::{Component, Properties}; + // pub(crate) use crate::component::Properties; use crate::context::hooks::Hook; pub(crate) use crate::context::Context; pub(crate) use crate::error::{Error, Result}; @@ -99,7 +99,15 @@ pub(crate) mod innerlude { // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub type FC

= for<'a> fn(Context<'a>, &'a P) -> VNode<'a>; + pub type FC

= for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; + + mod fc2 { + use super::*; + } + // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree; + // pub type FC

= for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree; + // pub type FC

= for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>; + // pub type FC

= for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>; // pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; // TODO @Jon, fix this @@ -116,7 +124,7 @@ pub(crate) mod innerlude { /// Re-export common types for ease of development use. /// Essential when working with the html! macro pub mod prelude { - pub use crate::component::{Component, Properties}; + // pub use crate::component::Properties; pub use crate::context::Context; use crate::nodes; pub use crate::virtual_dom::VirtualDom; diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index fae76a7aa..c80fd4b0d 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -1,6 +1,7 @@ //! Helpers for building virtual DOM VNodes. use crate::{ + innerlude::VComponent, nodes::{Attribute, Listener, NodeKey, VNode}, prelude::VElement, }; @@ -1079,3 +1080,8 @@ pub fn on<'a, 'b, F: 'static>( callback: bump.alloc(callback), } } + +pub fn virtual_child<'a, T>(bump: &'a Bump, props: T, f: crate::innerlude::FC) -> VNode<'a> { + todo!() + // VNode::Component() +} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 43ce30155..38db1a9e8 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -12,6 +12,10 @@ pub use velement::{Attribute, Listener, NodeKey}; pub use vnode::VNode; pub use vtext::VText; +/// A domtree represents the result of "Viewing" the context +/// It's a placeholder over vnodes, to make working with lifetimes easier +pub struct DomTree; + /// Tools for the base unit of the virtual dom - the VNode /// VNodes are intended to be quickly-allocated, lightweight enum values. /// @@ -20,6 +24,7 @@ pub use vtext::VText; mod vnode { use super::*; + #[derive(Debug)] pub enum VNode<'src> { /// An element node (node type `ELEMENT_NODE`). Element(&'src VElement<'src>), @@ -92,8 +97,9 @@ mod vnode { mod velement { use super::*; - use std::collections::HashMap; + use std::{collections::HashMap, fmt::Debug}; + #[derive(Debug)] pub struct VElement<'a> { /// Elements have a tag name, zero or more attributes, and zero or more pub key: NodeKey, @@ -186,6 +192,13 @@ mod velement { // /// The callback to invoke when the event happens. pub(crate) callback: &'bump (dyn Fn(())), } + impl Debug for Listener<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Listener") + .field("event", &self.event) + .finish() + } + } /// The key for keyed children. /// @@ -231,7 +244,7 @@ mod velement { } mod vtext { - #[derive(PartialEq, serde::Serialize, serde::Deserialize)] + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct VText<'bump> { pub text: &'bump str, } @@ -251,28 +264,30 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::innerlude::{Properties, FC}; + use crate::innerlude::FC; use std::{any::TypeId, fmt, future::Future, marker::PhantomData}; use super::VNode; + #[derive(Debug)] pub struct VComponent<'src> { _p: PhantomData<&'src ()>, - props: Box, + props: Box, // props: Box, caller: *const (), } impl<'a> VComponent<'a> { - pub fn new(caller: FC

, props: P) -> Self { + pub fn new

(caller: FC

, props: P) -> Self { let caller = caller as *const (); let props = Box::new(props); - Self { - _p: PhantomData {}, - props, - caller, - } + todo!() + // Self { + // _p: PhantomData {}, + // props, + // caller, + // } } } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index bf6ae76bc..d2be5d4c1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -33,39 +33,39 @@ pub struct Scope { // Map to the parent pub parent: Option, - // todo, do better with the active frame stuff - // somehow build this vnode with a lifetime tied to self - // This root node has "static" lifetime, but it's really not static. - // It's goverened by the oldest of the two frames and is switched every time a new render occurs - // Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref - // ! do not copy this reference are things WILL break ! pub frames: ActiveFrame, // IE Which listeners need to be woken up? pub listeners: Vec>, + // lying, cheating reference >:( pub props: Box, + // pub props: Box, // - pub props_type: TypeId, - pub caller: *const i32, + // pub props_type: TypeId, + pub caller: *const (), } impl Scope { // create a new scope from a function - pub fn new( - // pub(crate) fn new( - f: FC, - props: impl Properties + 'static, + pub fn new<'a, P1, P2: 'static>( + // pub fn new<'a, P: Properties, PFree: P + 'a, PLocked: P + 'static>( + f: FC, + props: P1, parent: Option, - ) -> Self { + ) -> Self +// where + // PFree: 'a, + // PLocked: 'static, + { // Capture the props type - let props_type = TypeId::of::(); + // let props_type = TypeId::of::

(); let hook_arena = typed_arena::Arena::new(); let hooks = RefCell::new(Vec::new()); // Capture the caller - let caller = f as *const i32; + let caller = f as *const (); let listeners = Vec::new(); @@ -80,12 +80,20 @@ impl Scope { }; let frames = ActiveFrame::from_frames(old_frame, new_frame); + + // box the props let props = Box::new(props); + // erase the lifetime + // we'll manage this with dom lifecycle + + let props = unsafe { std::mem::transmute::<_, Box>(props) }; + + // todo!() Self { hook_arena, hooks, - props_type, + // props_type, caller, frames, listeners, @@ -94,13 +102,10 @@ impl Scope { } } - /// Update this component's props with a new set of props + /// Update this component's props with a new set of props, remotely /// /// - pub(crate) fn update_props( - &mut self, - new_props: Box

, - ) -> crate::error::Result<()> { + pub(crate) fn update_props<'a, P>(&self, new_props: P) -> crate::error::Result<()> { Ok(()) } @@ -108,7 +113,7 @@ impl Scope { /// This function downcasts the function pointer based on the stored props_type /// /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) - pub fn run<'bump, P: Properties + Sized + 'static>(&'bump mut self) { + pub fn run<'bump, PLocked: Sized + 'static>(&'bump mut self) { let frame = { let frame = self.frames.next(); frame.bump.reset(); @@ -123,6 +128,18 @@ impl Scope { _p: PhantomData {}, }; + unsafe { + // we use plocked to be able to remove the borrowed lifetime + // these lifetimes could be very broken, so we need to dynamically manage them + let caller = std::mem::transmute::<*const (), FC>(self.caller); + let props = self.props.downcast_ref::().unwrap(); + let nodes: DomTree = caller(ctx, props); + todo!("absorb domtree into self") + // let nodes: VNode<'bump> = caller(ctx, props); + + // let unsafe_node = std::mem::transmute::, VNode<'static>>(nodes); + // frame.head_node = unsafe_node; + } /* SAFETY ALERT @@ -135,9 +152,6 @@ impl Scope { This is safe because we check that the generic type matches before casting. */ - let caller = unsafe { std::mem::transmute::<*const i32, FC

>(self.caller) }; - let nodes: VNode<'bump> = caller(ctx, self.props.downcast_ref::

().unwrap()); - /* SAFETY ALERT @@ -149,9 +163,6 @@ impl Scope { - The VNode has a private API and can only be used from accessors. - Public API cannot drop or destructure VNode */ - - let unsafe_node = unsafe { std::mem::transmute::, VNode<'static>>(nodes) }; - frame.head_node = unsafe_node; } /// Accessor to get the root node and its children (safely)\ @@ -159,6 +170,7 @@ impl Scope { pub fn current_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.current_head_node() } + pub fn prev_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> { todo!() } @@ -169,6 +181,12 @@ pub struct BumpFrame { pub head_node: VNode<'static>, } +// todo, do better with the active frame stuff +// somehow build this vnode with a lifetime tied to self +// This root node has "static" lifetime, but it's really not static. +// It's goverened by the oldest of the two frames and is switched every time a new render occurs +// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref +// ! do not copy this reference are things WILL break ! pub struct ActiveFrame { pub idx: AtomicUsize, pub frames: [BumpFrame; 2], @@ -183,7 +201,7 @@ impl ActiveFrame { } fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { - let cur_idx = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed); + let cur_idx = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed) % 1; let raw_node = &self.frames[cur_idx]; unsafe { let unsafe_head = &raw_node.head_node; @@ -202,3 +220,120 @@ impl ActiveFrame { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scope() { + let example: FC<()> = |ctx, props| { + use crate::builder::*; + ctx.view(|b| div(b).child(text("a")).finish()) + }; + + let props = (); + let parent = None; + let scope = Scope::new::<(), ()>(example, props, parent); + } + + #[derive(Debug)] + struct ExampleProps<'src> { + name: &'src String, + } + // impl<'src> Properties<'src> for ExampleProps<'src> {} + + #[derive(Debug)] + struct EmptyProps<'src> { + name: &'src String, + } + // impl<'src> Properties<'src> for EmptyProps<'src> {} + + use crate::{builder::*, hooks::use_ref}; + + fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps) -> DomTree { + // fn example_fc<'a>(ctx: Context<'a>, props: &'a EmptyProps<'a>) -> DomTree { + let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string()); + // let (content, _): (&'a String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string()); + // let (text, set_val) = crate::hooks::use_state(&ctx, || "abcd".to_string()); + + let childprops: ExampleProps<'a> = ExampleProps { name: content }; + // let childprops: ExampleProps<'a> = ExampleProps { name: content }; + ctx.view(move |b: &'a Bump| { + div(b) + .child(text(props.name)) + // .child(text(props.name)) + .child(virtual_child::(b, childprops, child_example)) + // .child(virtual_child::>(b, childprops, CHILD)) + // as for<'scope> fn(Context<'_>, &'scope ExampleProps<'scope>) -> DomTree + // |ctx, pops| todo!(), + // .child(virtual_child::<'a>( + // b, + // child_example, + // ExampleProps { name: text }, + // )) + .finish() + }) + } + + fn child_example(ctx: Context, props: &ExampleProps) -> DomTree { + ctx.view(move |b| { + div(b) + .child(text(props.name)) + // + .finish() + }) + } + + static CHILD: FC = |ctx, props: &'_ ExampleProps| { + // todo!() + ctx.view(move |b| { + div(b) + .child(text(props.name)) + // + .finish() + }) + }; + #[test] + fn test_borrowed_scope() { + // use crate::builder::*; + + let example: FC = |ctx, props| { + // render counter + // let mut val = crate::hooks::use_ref(&ctx, || 0); + // val.modify(|f| { + // *f += 1; + // }); + // dbg!(val.current()); + // only needs to be valid when ran? + // can only borrow from parent? + // props are boxed in parent's scope? + // passing complex structures down to child? + // stored value + // let (text, set_val) = crate::hooks::use_state(&ctx, || "abcd".to_string()); + + ctx.view(move |b| { + todo!() + // div(b) + // // .child(text(props.name)) + // // .child(virtual_child(b, CHILD, ExampleProps { name: val.as_str() })) + // .child(virtual_child(b, CHILD, ExampleProps { name: })) + // .finish() + }) + }; + + let source_text = "abcd123".to_string(); + let props = ExampleProps { name: &source_text }; + + // let parent = None; + // let mut scope = + // Scope::new::(example, EmptyProps { name: &() }, parent); + // scope.run::(); + // scope.run::(); + // scope.run::(); + // scope.run::(); + // scope.run::(); + // let nodes = scope.current_root_node(); + // dbg!(nodes); + } +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 3118c3a9f..bab7481bf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,5 +1,9 @@ // use crate::{changelist::EditList, nodes::VNode}; -use crate::{changelist::{self, EditList}, dodriodiff::DiffMachine, nodes::VNode}; +use crate::{ + changelist::{self, EditList}, + dodriodiff::DiffMachine, + nodes::VNode, +}; use crate::{events::EventTrigger, innerlude::*}; use any::Any; use bumpalo::Bump; @@ -51,7 +55,7 @@ impl VirtualDom { /// /// 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. - pub fn new_with_props(root: FC

, root_props: P) -> Self { + pub fn new_with_props(root: FC

, root_props: P) -> Self { // 1. Create the component arena // 2. Create the base scope (can never be removed) // 3. Create the lifecycle queue @@ -82,7 +86,7 @@ impl VirtualDom { } /// With access to the virtual dom, schedule an update to the Root component's props - pub fn update_props(&mut self, new_props: P) -> Result<()> { + pub fn update_props(&mut self, new_props: P) -> Result<()> { // Ensure the props match if TypeId::of::

() != self._root_prop_type { return Err(Error::WrongProps); @@ -262,10 +266,10 @@ pub enum LifecycleType { Mount { to: Option, under: usize, - props: Box, + props: Box, }, PropsChanged { - props: Box, + props: Box, }, Rendered, Mounted, @@ -279,12 +283,7 @@ pub enum LifecycleType { impl LifecycleEvent { // helper method for shortcutting to the enum type // probably not necessary - fn mount( - which: Index, - to: Option, - under: usize, - props: P, - ) -> Self { + fn mount(which: Index, to: Option, under: usize, props: P) -> Self { Self { component_index: which, event_type: LifecycleType::Mount { diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 9da398350..6888978f1 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -24,7 +24,7 @@ use dioxus::prelude::VElement; pub use dioxus_core as dioxus; use dioxus_core::{ events::EventTrigger, - prelude::{bumpalo::Bump, html, DiffMachine, Properties, VNode, VirtualDom, FC}, + prelude::{bumpalo::Bump, html, DiffMachine, VNode, VirtualDom, FC}, }; use futures::{channel::mpsc, future, SinkExt, StreamExt}; use mpsc::UnboundedSender; @@ -50,7 +50,7 @@ impl WebsysRenderer { /// Automatically progresses the creation of the VNode tree to completion. /// /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` - pub fn new_with_props(root: FC, root_props: T) -> Self { + pub fn new_with_props(root: FC, root_props: T) -> Self { Self::from_vdom(VirtualDom::new_with_props(root, root_props)) } diff --git a/packages/webview/Cargo.toml b/packages/webview/Cargo.toml index 55dc8a43a..215f3d340 100644 --- a/packages/webview/Cargo.toml +++ b/packages/webview/Cargo.toml @@ -7,7 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -web-view = "0.7.2" +web-view = { git = "https://github.com/Boscop/web-view" } +# web-view = "0.7.2" dioxus-core = { path = "../core" } anyhow = "1.0.38" argh = "0.1.4"