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!{ +
(2)
+ // child_props: Map
{
- type Props = P;
+// // Auto implement component for a FC
+// // Calling the FC is the same as "rendering" it
+// impl {
+// 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:: = 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 , 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 ();
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 ,
- ) -> 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 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:: , root_props: P) -> Self {
+ pub fn new_with_props , 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 () != self._root_prop_type {
return Err(Error::WrongProps);
@@ -262,10 +266,10 @@ pub enum LifecycleType {
Mount {
to: Option