Browse Source

finishing touches and benchmarks

Evan Almloff 3 năm trước cách đây
mục cha
commit
5b25500c0b

+ 6 - 2
Cargo.toml

@@ -28,6 +28,8 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
 
 dioxus-liveview = { path = "./packages/liveview", optional = true }
 
+dioxus-native-core = { path = "./packages/native-core", optional = true }
+
 # dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
 # dioxus-rsx = { path = "./packages/rsx", optional = true }
 # macro = ["dioxus-core-macro", "dioxus-rsx"]
@@ -45,6 +47,7 @@ ayatana = ["dioxus-desktop/ayatana"]
 router = ["dioxus-router"]
 tui = ["dioxus-tui"]
 liveview = ["dioxus-liveview"]
+native-core = ["dioxus-native-core"]
 
 
 [workspace]
@@ -90,5 +93,6 @@ harness = false
 name = "jsframework"
 harness = false
 
-[profile.release]
-debug = true
+[[bench]]
+name = "tui_update"
+harness = false

+ 196 - 0
benches/tui_update.rs

@@ -0,0 +1,196 @@
+use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
+use dioxus::prelude::*;
+use dioxus_tui::TuiContext;
+
+criterion_group!(mbenches, update_boxes);
+criterion_main!(mbenches);
+
+/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
+fn update_boxes(c: &mut Criterion) {
+    let mut group = c.benchmark_group("Update boxes");
+    // We can override the configuration on a per-group level
+    group.sample_size(10);
+
+    // We can also use loops to define multiple benchmarks, even over multiple dimensions.
+    for size in 1..=6 {
+        let parameter_string = format!("{}", 5 * size);
+        group.bench_with_input(
+            BenchmarkId::new("size", parameter_string),
+            &size,
+            |b, size| {
+                b.iter(|| match size {
+                    1 => dioxus::tui::launch(app5),
+                    2 => dioxus::tui::launch(app10),
+                    3 => dioxus::tui::launch(app15),
+                    4 => dioxus::tui::launch(app20),
+                    5 => dioxus::tui::launch(app25),
+                    6 => dioxus::tui::launch(app30),
+                    _ => (),
+                })
+            },
+        );
+    }
+}
+
+#[derive(Props, PartialEq)]
+struct BoxProps {
+    x: usize,
+    y: usize,
+    hue: f32,
+    alpha: f32,
+}
+#[allow(non_snake_case)]
+fn Box(cx: Scope<BoxProps>) -> Element {
+    let count = use_state(&cx, || 0);
+
+    let x = cx.props.x * 2;
+    let y = cx.props.y * 2;
+    let hue = cx.props.hue;
+    let display_hue = cx.props.hue as u32 / 10;
+    let count = count.get();
+    let alpha = cx.props.alpha + (count % 100) as f32;
+
+    cx.render(rsx! {
+        div {
+            left: "{x}%",
+            top: "{y}%",
+            width: "100%",
+            height: "100%",
+            background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
+            align_items: "center",
+            p{"{display_hue:03}"}
+        }
+    })
+}
+
+#[derive(Props, PartialEq)]
+struct GridProps {
+    size: usize,
+}
+#[allow(non_snake_case)]
+fn Grid(cx: Scope<GridProps>) -> Element {
+    let size = cx.props.size;
+    let count = use_state(&cx, || 0);
+    let counts = use_ref(&cx, || vec![0; size * size]);
+
+    let ctx: TuiContext = cx.consume_context().unwrap();
+    if *count.get() + 1 >= (size * size) {
+        ctx.quit();
+    } else {
+        counts.with_mut(|c| {
+            let i = *count.current();
+            c[i] += 1;
+            c[i] = c[i] % 360;
+        });
+        count.with_mut(|i| {
+            *i += 1;
+            *i = *i % (size * size);
+        });
+    }
+
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            flex_direction: "column",
+            (0..size).map(|x|
+                    {
+                    cx.render(rsx! {
+                        div{
+                            width: "100%",
+                            height: "100%",
+                            flex_direction: "row",
+                            (0..size).map(|y|
+                                {
+                                    let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
+                                    let key = format!("{}-{}", x, y);
+                                    cx.render(rsx! {
+                                        Box{
+                                            x: x,
+                                            y: y,
+                                            alpha: 100.0,
+                                            hue: alpha,
+                                            key: "{key}",
+                                        }
+                                    })
+                                }
+                            )
+                        }
+                    })
+                }
+            )
+        }
+    })
+}
+
+fn app5(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: 5,
+            }
+        }
+    })
+}
+
+fn app10(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: 10,
+            }
+        }
+    })
+}
+
+fn app15(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: 15,
+            }
+        }
+    })
+}
+
+fn app20(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: 20,
+            }
+        }
+    })
+}
+
+fn app25(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: 25,
+            }
+        }
+    })
+}
+
+fn app30(cx: Scope) -> Element {
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            Grid{
+                size: 30,
+            }
+        }
+    })
+}

+ 0 - 88
examples/tui_stress_test.rs

@@ -1,88 +0,0 @@
-use dioxus::prelude::*;
-
-fn main() {
-    dioxus::tui::launch_cfg(
-        app,
-        dioxus::tui::Config {
-            rendering_mode: dioxus::tui::RenderingMode::Rgb,
-        },
-    );
-}
-
-#[derive(Props, PartialEq)]
-struct BoxProps {
-    x: i32,
-    y: i32,
-    hue: f32,
-    alpha: f32,
-}
-#[allow(non_snake_case)]
-fn Box(cx: Scope<BoxProps>) -> Element {
-    let count = use_state(&cx, || 0);
-
-    use_future(&cx, (), move |_| {
-        let count = count.to_owned();
-        let update = cx.schedule_update();
-        async move {
-            loop {
-                count.with_mut(|i| *i += 1);
-                tokio::time::sleep(std::time::Duration::from_millis(800)).await;
-                update();
-            }
-        }
-    });
-
-    let x = cx.props.x * 2;
-    let y = cx.props.y * 2;
-    let hue = cx.props.hue;
-    let count = count.get();
-    let alpha = cx.props.alpha + (count % 100) as f32;
-
-    cx.render(rsx! {
-        div {
-            left: "{x}%",
-            top: "{y}%",
-            width: "100%",
-            height: "100%",
-            background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
-            align_items: "center",
-            p{"{count}"}
-        }
-    })
-}
-
-fn app(cx: Scope) -> Element {
-    let steps = 50;
-    cx.render(rsx! {
-        div{
-            width: "100%",
-            height: "100%",
-            flex_direction: "column",
-            (0..=steps).map(|x|
-                {
-                    let hue = x as f32*360.0/steps as f32;
-                    cx.render(rsx! {
-                        div{
-                            width: "100%",
-                            height: "100%",
-                            flex_direction: "row",
-                            (0..=steps).map(|y|
-                                {
-                                    let alpha = y as f32*100.0/steps as f32;
-                                    cx.render(rsx! {
-                                        Box{
-                                            x: x,
-                                            y: y,
-                                            alpha: alpha,
-                                            hue: hue,
-                                        }
-                                    })
-                                }
-                            )
-                        }
-                    })
-                }
-            )
-        }
-    })
-}

+ 4 - 11
packages/native-core/src/layout_attributes.rs

@@ -32,12 +32,7 @@
 use stretch2::{prelude::*, style::PositionType};
 
 /// applies the entire html namespace defined in dioxus-html
-pub fn apply_layout_attributes(
-    //
-    name: &str,
-    value: &str,
-    style: &mut Style,
-) {
+pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
     match name {
         "align-content"
         | "align-items"
@@ -253,6 +248,7 @@ pub fn apply_layout_attributes(
     }
 }
 
+/// a relative or absolute size
 #[derive(Clone, Copy, PartialEq, Debug)]
 pub enum UnitSystem {
     Percent(f32),
@@ -268,6 +264,7 @@ impl Into<Dimension> for UnitSystem {
     }
 }
 
+/// parse relative or absolute value
 pub fn parse_value(value: &str) -> Option<UnitSystem> {
     if value.ends_with("px") {
         if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
@@ -605,11 +602,7 @@ fn apply_align(name: &str, value: &str, style: &mut Style) {
     }
 }
 
-pub fn apply_size(_name: &str, _value: &str, _style: &mut Style) {
-    //
-}
-
-pub fn apply_margin(name: &str, value: &str, style: &mut Style) {
+fn apply_margin(name: &str, value: &str, style: &mut Style) {
     match parse_value(value) {
         Some(UnitSystem::Percent(v)) => match name {
             "margin" => {

+ 1 - 2
packages/native-core/src/lib.rs

@@ -1,3 +1,2 @@
-pub mod client_tree;
-pub mod layout;
 pub mod layout_attributes;
+pub mod real_dom;

+ 17 - 15
packages/native-core/src/client_tree.rs → packages/native-core/src/real_dom.rs

@@ -6,20 +6,20 @@ use std::{
 
 use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
 
-/// A tree that can sync with dioxus mutations backed by a hashmap.
-/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
-/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state].
+/// A tree that can sync with the VirtualDom mutations intended for use in lazy renderers.
+/// The render state passes from parent to children and or accumulates state from children to parents.
+/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] to update the tree and [Tree::update_state] to update the state of the nodes.
 #[derive(Debug)]
-pub struct ClientTree<US: BubbledUpState = (), DS: PushedDownState = ()> {
+pub struct RealDom<US: BubbledUpState = (), DS: PushedDownState = ()> {
     root: usize,
     nodes: Vec<Option<TreeNode<US, DS>>>,
     nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
     node_stack: smallvec::SmallVec<[usize; 10]>,
 }
 
-impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
-    pub fn new() -> ClientTree<US, DS> {
-        ClientTree {
+impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
+    pub fn new() -> RealDom<US, DS> {
+        RealDom {
             root: 0,
             nodes: {
                 let mut v = Vec::new();
@@ -187,6 +187,7 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
         ds_ctx: &mut DS::Ctx,
     ) -> Option<FxHashSet<usize>> {
         let mut to_rerender = FxHashSet::default();
+        to_rerender.extend(nodes_updated.iter());
         let mut nodes_updated: Vec<_> = nodes_updated
             .into_iter()
             .map(|id| (id, self[id].height))
@@ -296,10 +297,11 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
         }
     }
 
+    // remove a node and it's children from the tree.
     fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> {
         // We do not need to remove the node from the parent's children list for children.
         fn inner<US: BubbledUpState, DS: PushedDownState>(
-            tree: &mut ClientTree<US, DS>,
+            tree: &mut RealDom<US, DS>,
             id: usize,
         ) -> Option<TreeNode<US, DS>> {
             let mut node = tree.nodes[id as usize].take()?;
@@ -418,9 +420,9 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
     }
 
     /// Call a function for each node in the tree, depth first.
-    pub fn traverse(&self, mut f: impl FnMut(&TreeNode<US, DS>)) {
+    pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode<US, DS>)) {
         fn inner<US: BubbledUpState, DS: PushedDownState>(
-            tree: &ClientTree<US, DS>,
+            tree: &RealDom<US, DS>,
             id: ElementId,
             f: &mut impl FnMut(&TreeNode<US, DS>),
         ) {
@@ -446,7 +448,7 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
     }
 }
 
-impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for ClientTree<US, DS> {
+impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> {
     type Output = TreeNode<US, DS>;
 
     fn index(&self, idx: usize) -> &Self::Output {
@@ -454,7 +456,7 @@ impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for ClientTree<US, DS
     }
 }
 
-impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for ClientTree<US, DS> {
+impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for RealDom<US, DS> {
     type Output = TreeNode<US, DS>;
 
     fn index(&self, idx: ElementId) -> &Self::Output {
@@ -462,12 +464,12 @@ impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for ClientTree<US
     }
 }
 
-impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for ClientTree<US, DS> {
+impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for RealDom<US, DS> {
     fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
         self.get_mut(idx).expect("Node does not exist")
     }
 }
-impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for ClientTree<US, DS> {
+impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US, DS> {
     fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
         &mut self[idx.0]
     }
@@ -557,7 +559,7 @@ impl PushedDownState for () {
     fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {}
 }
 
-/// This state is derived from children. For example a non-flexbox div's size could be derived from the size of children.
+/// This state is derived from children. For example a node's size could be derived from the size of children.
 /// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
 /// Called at most once per update.
 pub trait BubbledUpState: Default + PartialEq + Clone {

+ 3 - 3
packages/native-core/tests/change_nodes.rs

@@ -2,7 +2,7 @@ use dioxus_core::VNode;
 use dioxus_core::*;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
-use dioxus_native_core::client_tree::ClientTree;
+use dioxus_native_core::real_dom::RealDom;
 use std::cell::Cell;
 
 #[test]
@@ -20,7 +20,7 @@ fn tree_remove_node() {
         }
     });
 
-    let mut tree: ClientTree<(), ()> = ClientTree::new();
+    let mut tree: RealDom<(), ()> = RealDom::new();
 
     let _to_update = tree.apply_mutations(vec![mutations]);
     let child_div = VElement {
@@ -92,7 +92,7 @@ fn tree_add_node() {
         div{}
     });
 
-    let mut tree: ClientTree<(), ()> = ClientTree::new();
+    let mut tree: RealDom<(), ()> = RealDom::new();
 
     let _to_update = tree.apply_mutations(vec![mutations]);
 

+ 3 - 3
packages/native-core/tests/initial_build.rs

@@ -4,7 +4,7 @@ use dioxus_core::VNode;
 use dioxus_core::*;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
-use dioxus_native_core::client_tree::ClientTree;
+use dioxus_native_core::real_dom::RealDom;
 
 #[test]
 fn tree_initial_build_simple() {
@@ -21,7 +21,7 @@ fn tree_initial_build_simple() {
         div{}
     });
 
-    let mut tree: ClientTree<(), ()> = ClientTree::new();
+    let mut tree: RealDom<(), ()> = RealDom::new();
 
     let _to_update = tree.apply_mutations(vec![mutations]);
     let root_div = VElement {
@@ -60,7 +60,7 @@ fn tree_initial_build_with_children() {
         }
     });
 
-    let mut tree: ClientTree<(), ()> = ClientTree::new();
+    let mut tree: RealDom<(), ()> = RealDom::new();
 
     let _to_update = tree.apply_mutations(vec![mutations]);
     let first_text = VText {

+ 8 - 8
packages/native-core/tests/state.rs

@@ -2,7 +2,7 @@ use dioxus_core::VNode;
 use dioxus_core::*;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
-use dioxus_native_core::client_tree::*;
+use dioxus_native_core::real_dom::*;
 
 #[derive(Debug, Clone, PartialEq, Default)]
 struct CallCounter(u32);
@@ -77,7 +77,7 @@ fn tree_state_initial() {
         }
     });
 
-    let mut tree: ClientTree<BubbledUpStateTester, PushedDownStateTester> = ClientTree::new();
+    let mut tree: RealDom<BubbledUpStateTester, PushedDownStateTester> = RealDom::new();
 
     let nodes_updated = tree.apply_mutations(vec![mutations]);
     let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42);
@@ -178,12 +178,12 @@ fn tree_state_reduce_initally_called_minimally() {
         }
     });
 
-    let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
+    let mut tree: RealDom<CallCounter, CallCounter> = RealDom::new();
 
     let nodes_updated = tree.apply_mutations(vec![mutations]);
     let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
 
-    tree.traverse(|n| {
+    tree.traverse_depth_first(|n| {
         assert_eq!(n.up_state.0, 1);
         assert_eq!(n.down_state.0, 1);
     });
@@ -233,7 +233,7 @@ fn tree_state_reduce_down_called_minimally_on_update() {
         }
     });
 
-    let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
+    let mut tree: RealDom<CallCounter, CallCounter> = RealDom::new();
 
     let nodes_updated = tree.apply_mutations(vec![mutations]);
     let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
@@ -249,7 +249,7 @@ fn tree_state_reduce_down_called_minimally_on_update() {
     }]);
     let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
 
-    tree.traverse(|n| {
+    tree.traverse_depth_first(|n| {
         assert_eq!(n.down_state.0, 2);
     });
 }
@@ -298,7 +298,7 @@ fn tree_state_reduce_up_called_minimally_on_update() {
         }
     });
 
-    let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
+    let mut tree: RealDom<CallCounter, CallCounter> = RealDom::new();
 
     let nodes_updated = tree.apply_mutations(vec![mutations]);
     let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
@@ -314,7 +314,7 @@ fn tree_state_reduce_up_called_minimally_on_update() {
     }]);
     let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
 
-    tree.traverse(|n| {
+    tree.traverse_depth_first(|n| {
         assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 });
     });
 }

+ 6 - 8
packages/tui/src/hooks.rs

@@ -5,10 +5,7 @@ use dioxus_core::*;
 use fxhash::{FxHashMap, FxHashSet};
 
 use dioxus_html::{on::*, KeyCode};
-use dioxus_native_core::{
-    client_tree::{ClientTree, TreeNode},
-    layout::StretchLayout,
-};
+use dioxus_native_core::real_dom::{RealDom, TreeNode};
 use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
 use std::{
     any::Any,
@@ -19,6 +16,7 @@ use std::{
 };
 use stretch2::{prelude::Layout, Stretch};
 
+use crate::layout::StretchLayout;
 use crate::style_attributes::StyleModifier;
 
 // a wrapper around the input state for easier access
@@ -169,7 +167,7 @@ impl InnerInputState {
         evts: &mut Vec<EventCore>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Stretch,
-        tree: &mut ClientTree<StretchLayout, StyleModifier>,
+        tree: &mut RealDom<StretchLayout, StyleModifier>,
     ) {
         let previous_mouse = self
             .mouse
@@ -194,7 +192,7 @@ impl InnerInputState {
         previous_mouse: Option<(MouseData, Vec<u16>)>,
         resolved_events: &mut Vec<UserEvent>,
         layout: &Stretch,
-        tree: &mut ClientTree<StretchLayout, StyleModifier>,
+        tree: &mut RealDom<StretchLayout, StyleModifier>,
     ) {
         struct Data<'b> {
             new_pos: (i32, i32),
@@ -219,7 +217,7 @@ impl InnerInputState {
             will_bubble: &mut FxHashSet<ElementId>,
             resolved_events: &mut Vec<UserEvent>,
             node: &TreeNode<StretchLayout, StyleModifier>,
-            tree: &ClientTree<StretchLayout, StyleModifier>,
+            tree: &RealDom<StretchLayout, StyleModifier>,
         ) {
             // only trigger event if the event was not triggered already by a child
             if will_bubble.insert(node.id) {
@@ -546,7 +544,7 @@ impl RinkInputHandler {
     pub fn get_events<'a>(
         &self,
         layout: &Stretch,
-        tree: &mut ClientTree<StretchLayout, StyleModifier>,
+        tree: &mut RealDom<StretchLayout, StyleModifier>,
     ) -> Vec<UserEvent> {
         let mut resolved_events = Vec::new();
 

+ 4 - 10
packages/native-core/src/layout.rs → packages/tui/src/layout.rs

@@ -1,15 +1,9 @@
-use crate::client_tree::BubbledUpState;
 use dioxus_core::*;
+use dioxus_native_core::layout_attributes::apply_layout_attributes;
+use dioxus_native_core::real_dom::BubbledUpState;
 use stretch2::prelude::*;
 
-use crate::layout_attributes::apply_layout_attributes;
-
-/*
-The layout system uses the lineheight as one point.
-
-stretch uses fractional points, so we can rasterize if we need too, but not with characters
-this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger
-*/
+/// the size
 #[derive(Clone, PartialEq, Default, Debug)]
 pub struct StretchLayout {
     pub style: Style,
@@ -19,7 +13,7 @@ pub struct StretchLayout {
 impl BubbledUpState for StretchLayout {
     type Ctx = Stretch;
 
-    // Although we pass in the parent, the state of RinkLayout must only depend on the parent for optimiztion purposes
+    /// Setup the layout
     fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx)
     where
         I: Iterator<Item = &'a Self>,

+ 50 - 48
packages/tui/src/lib.rs

@@ -9,16 +9,20 @@ use crossterm::{
 };
 use dioxus_core::exports::futures_channel::mpsc::unbounded;
 use dioxus_core::*;
-use dioxus_native_core::{client_tree::ClientTree, layout::StretchLayout};
-use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
+use dioxus_native_core::real_dom::RealDom;
+use futures::{
+    channel::mpsc::{UnboundedReceiver, UnboundedSender},
+    pin_mut, StreamExt,
+};
+use layout::StretchLayout;
 use std::{io, time::Duration};
 use stretch2::{prelude::Size, Stretch};
 use style_attributes::StyleModifier;
-use tokio::time::Instant;
 use tui::{backend::CrosstermBackend, Terminal};
 
 mod config;
 mod hooks;
+mod layout;
 mod render;
 mod style;
 mod style_attributes;
@@ -28,6 +32,16 @@ pub use config::*;
 pub use hooks::*;
 pub use render::*;
 
+#[derive(Clone)]
+pub struct TuiContext {
+    tx: UnboundedSender<InputEvent>,
+}
+impl TuiContext {
+    pub fn quit(&self) {
+        self.tx.unbounded_send(InputEvent::Close).unwrap();
+    }
+}
+
 pub fn launch(app: Component<()>) {
     launch_cfg(app, Config::default())
 }
@@ -40,9 +54,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
 
     let (handler, state) = RinkInputHandler::new(rx, cx);
 
+    // Setup input handling
+    let (event_tx, event_rx) = unbounded();
+    let event_tx_clone = event_tx.clone();
+    std::thread::spawn(move || {
+        let tick_rate = Duration::from_millis(1000);
+        loop {
+            if crossterm::event::poll(tick_rate).unwrap() {
+                // if crossterm::event::poll(timeout).unwrap() {
+                let evt = crossterm::event::read().unwrap();
+                if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
+                    break;
+                }
+            }
+        }
+    });
+
     cx.provide_root_context(state);
+    cx.provide_root_context(TuiContext { tx: event_tx_clone });
 
-    let mut tree: ClientTree<StretchLayout, StyleModifier> = ClientTree::new();
+    let mut tree: RealDom<StretchLayout, StyleModifier> = RealDom::new();
     let mutations = dom.rebuild();
     let to_update = tree.apply_mutations(vec![mutations]);
     let mut stretch = Stretch::new();
@@ -50,47 +81,22 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
         .update_state(&dom, to_update, &mut stretch, &mut ())
         .unwrap();
 
-    render_vdom(&mut dom, tx, handler, cfg, tree, stretch).unwrap();
+    render_vdom(&mut dom, tx, event_rx, handler, cfg, tree, stretch).unwrap();
 }
 
-pub fn render_vdom(
+fn render_vdom(
     vdom: &mut VirtualDom,
-    ctx: UnboundedSender<TermEvent>,
+    crossterm_event_sender: UnboundedSender<TermEvent>,
+    mut event_reciever: UnboundedReceiver<InputEvent>,
     handler: RinkInputHandler,
     cfg: Config,
-    mut tree: ClientTree<StretchLayout, StyleModifier>,
+    mut tree: RealDom<StretchLayout, StyleModifier>,
     mut stretch: Stretch,
 ) -> Result<()> {
-    // Setup input handling
-    let (tx, mut rx) = unbounded();
-    std::thread::spawn(move || {
-        let tick_rate = Duration::from_millis(100);
-        let mut last_tick = Instant::now();
-        loop {
-            // poll for tick rate duration, if no events, sent tick event.
-            let timeout = tick_rate
-                .checked_sub(last_tick.elapsed())
-                .unwrap_or_else(|| Duration::from_secs(0));
-
-            if crossterm::event::poll(timeout).unwrap() {
-                let evt = crossterm::event::read().unwrap();
-                tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
-            }
-
-            if last_tick.elapsed() >= tick_rate {
-                tx.unbounded_send(InputEvent::Tick).unwrap();
-                last_tick = Instant::now();
-            }
-        }
-    });
-
     tokio::runtime::Builder::new_current_thread()
         .enable_all()
         .build()?
         .block_on(async {
-            /*
-            Get the terminal to calcualte the layout from
-            */
             enable_raw_mode().unwrap();
             let mut stdout = std::io::stdout();
             execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
@@ -103,10 +109,9 @@ pub fn render_vdom(
 
             loop {
                 /*
-                -> collect all the nodes
-                -> resolve events
                 -> render the nodes in the right place with tui/crossterm
-                -> rendering
+                -> wait for changes
+                -> resolve events
                 -> lazily update the layout and style based on nodes changed
 
                 use simd to compare lines for diffing?
@@ -119,11 +124,11 @@ pub fn render_vdom(
                     terminal.draw(|frame| {
                         // size is guaranteed to not change when rendering
                         let dims = frame.size();
-                        // println!("{dims:?}");
                         let width = dims.width;
                         let height = dims.height;
                         let root_id = tree.root_id();
                         let root_node = tree[root_id].up_state.node.unwrap();
+
                         stretch
                             .compute_layout(
                                 root_node,
@@ -138,18 +143,12 @@ pub fn render_vdom(
                     })?;
                 }
 
-                // resolve events before rendering
-                // todo: events do not trigger update?
-                for e in handler.get_events(&stretch, &mut tree) {
-                    vdom.handle_message(SchedulerMsg::Event(e));
-                }
-
                 use futures::future::{select, Either};
                 {
                     let wait = vdom.wait_for_work();
                     pin_mut!(wait);
 
-                    match select(wait, rx.next()).await {
+                    match select(wait, event_reciever.next()).await {
                         Either::Left((_a, _b)) => {
                             //
                         }
@@ -166,17 +165,21 @@ pub fn render_vdom(
                                     TermEvent::Resize(_, _) => redraw = true,
                                     TermEvent::Mouse(_) => {}
                                 },
-                                InputEvent::Tick => {} // tick
                                 InputEvent::Close => break,
                             };
 
                             if let InputEvent::UserInput(evt) = evt.unwrap() {
-                                ctx.unbounded_send(evt).unwrap();
+                                crossterm_event_sender.unbounded_send(evt).unwrap();
                             }
                         }
                     }
                 }
 
+                // resolve events before rendering
+                for e in handler.get_events(&stretch, &mut tree) {
+                    vdom.handle_message(SchedulerMsg::Event(e));
+                }
+                vdom.process_all_messages();
                 let mutations = vdom.work_with_deadline(|| false);
                 // updates the tree's nodes
                 let to_update = tree.apply_mutations(mutations);
@@ -200,7 +203,6 @@ pub fn render_vdom(
 
 enum InputEvent {
     UserInput(TermEvent),
-    Tick,
     #[allow(dead_code)]
     Close,
 }

+ 4 - 5
packages/tui/src/render.rs

@@ -1,7 +1,7 @@
+use crate::layout::StretchLayout;
 use dioxus_native_core::{
-    client_tree::{ClientTree, TreeNode},
-    layout::StretchLayout,
     layout_attributes::UnitSystem,
+    real_dom::{RealDom, TreeNode},
 };
 use std::io::Stdout;
 use stretch2::{
@@ -23,11 +23,11 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
 pub fn render_vnode<'a>(
     frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
     layout: &Stretch,
-    tree: &ClientTree<StretchLayout, StyleModifier>,
+    tree: &RealDom<StretchLayout, StyleModifier>,
     node: &TreeNode<StretchLayout, StyleModifier>,
     cfg: Config,
 ) {
-    use dioxus_native_core::client_tree::TreeNodeType;
+    use dioxus_native_core::real_dom::TreeNodeType;
 
     match &node.node_type {
         TreeNodeType::Placeholder => return,
@@ -51,7 +51,6 @@ pub fn render_vnode<'a>(
                 fn render(self, area: Rect, mut buf: RinkBuffer) {
                     for (i, c) in self.text.char_indices() {
                         let mut new_cell = RinkCell::default();
-                        // println!("{:?}", self.style);
                         new_cell.set_style(self.style);
                         new_cell.symbol = c.to_string();
                         buf.set(area.left() + i as u16, area.top(), new_cell);

+ 1 - 1
packages/tui/src/style.rs

@@ -28,7 +28,7 @@ impl RinkColor {
         } else {
             let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
             let [or, og, ob] = to_rgb(other).map(|e| e as u16);
-            let sa: u16 = self.alpha as u16;
+            let sa = self.alpha as u16;
             let rsa = 255 - sa;
             Color::Rgb(
                 ((sr * sa + or * rsa) / 255) as u8,

+ 1 - 1
packages/tui/src/style_attributes.rs

@@ -31,8 +31,8 @@
 
 use dioxus_core::{Attribute, VNode};
 use dioxus_native_core::{
-    client_tree::PushedDownState,
     layout_attributes::{parse_value, UnitSystem},
+    real_dom::PushedDownState,
 };
 
 use crate::style::{RinkColor, RinkStyle};