From 5b25500c0b76d8cfedeb3a3d2fbeaffd52432b39 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 30 Mar 2022 20:45:41 -0500 Subject: [PATCH] finishing touches and benchmarks --- Cargo.toml | 8 +- benches/tui_update.rs | 196 ++++++++++++++++++ examples/tui_stress_test.rs | 88 -------- packages/native-core/src/layout_attributes.rs | 15 +- packages/native-core/src/lib.rs | 3 +- .../src/{client_tree.rs => real_dom.rs} | 32 +-- packages/native-core/tests/change_nodes.rs | 6 +- packages/native-core/tests/initial_build.rs | 6 +- packages/native-core/tests/state.rs | 16 +- packages/tui/src/hooks.rs | 14 +- packages/{native-core => tui}/src/layout.rs | 14 +- packages/tui/src/lib.rs | 100 ++++----- packages/tui/src/render.rs | 9 +- packages/tui/src/style.rs | 2 +- packages/tui/src/style_attributes.rs | 2 +- 15 files changed, 305 insertions(+), 206 deletions(-) create mode 100644 benches/tui_update.rs delete mode 100644 examples/tui_stress_test.rs rename packages/native-core/src/{client_tree.rs => real_dom.rs} (95%) rename packages/{native-core => tui}/src/layout.rs (84%) diff --git a/Cargo.toml b/Cargo.toml index 5d6f01506..95900a1d2 100644 --- a/Cargo.toml +++ b/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 \ No newline at end of file +[[bench]] +name = "tui_update" +harness = false \ No newline at end of file diff --git a/benches/tui_update.rs b/benches/tui_update.rs new file mode 100644 index 000000000..6c77c9132 --- /dev/null +++ b/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) -> 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) -> 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, + } + } + }) +} diff --git a/examples/tui_stress_test.rs b/examples/tui_stress_test.rs deleted file mode 100644 index 72e72273d..000000000 --- a/examples/tui_stress_test.rs +++ /dev/null @@ -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) -> 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, - } - }) - } - ) - } - }) - } - ) - } - }) -} diff --git a/packages/native-core/src/layout_attributes.rs b/packages/native-core/src/layout_attributes.rs index afe7d177c..9518451fc 100644 --- a/packages/native-core/src/layout_attributes.rs +++ b/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 for UnitSystem { } } +/// parse relative or absolute value pub fn parse_value(value: &str) -> Option { if value.ends_with("px") { if let Ok(px) = value.trim_end_matches("px").parse::() { @@ -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" => { diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index 1dc8ad1c7..26c629446 100644 --- a/packages/native-core/src/lib.rs +++ b/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; diff --git a/packages/native-core/src/client_tree.rs b/packages/native-core/src/real_dom.rs similarity index 95% rename from packages/native-core/src/client_tree.rs rename to packages/native-core/src/real_dom.rs index 6b4017e8f..1d697ff4a 100644 --- a/packages/native-core/src/client_tree.rs +++ b/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 { +pub struct RealDom { root: usize, nodes: Vec>>, nodes_listening: FxHashMap<&'static str, FxHashSet>, node_stack: smallvec::SmallVec<[usize; 10]>, } -impl ClientTree { - pub fn new() -> ClientTree { - ClientTree { +impl RealDom { + pub fn new() -> RealDom { + RealDom { root: 0, nodes: { let mut v = Vec::new(); @@ -187,6 +187,7 @@ impl ClientTree { ds_ctx: &mut DS::Ctx, ) -> Option> { 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 ClientTree { } } + // remove a node and it's children from the tree. fn remove(&mut self, id: usize) -> Option> { // We do not need to remove the node from the parent's children list for children. fn inner( - tree: &mut ClientTree, + tree: &mut RealDom, id: usize, ) -> Option> { let mut node = tree.nodes[id as usize].take()?; @@ -418,9 +420,9 @@ impl ClientTree { } /// Call a function for each node in the tree, depth first. - pub fn traverse(&self, mut f: impl FnMut(&TreeNode)) { + pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode)) { fn inner( - tree: &ClientTree, + tree: &RealDom, id: ElementId, f: &mut impl FnMut(&TreeNode), ) { @@ -446,7 +448,7 @@ impl ClientTree { } } -impl Index for ClientTree { +impl Index for RealDom { type Output = TreeNode; fn index(&self, idx: usize) -> &Self::Output { @@ -454,7 +456,7 @@ impl Index for ClientTree Index for ClientTree { +impl Index for RealDom { type Output = TreeNode; fn index(&self, idx: ElementId) -> &Self::Output { @@ -462,12 +464,12 @@ impl Index for ClientTree IndexMut for ClientTree { +impl IndexMut for RealDom { fn index_mut(&mut self, idx: usize) -> &mut Self::Output { self.get_mut(idx).expect("Node does not exist") } } -impl IndexMut for ClientTree { +impl IndexMut for RealDom { 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 { diff --git a/packages/native-core/tests/change_nodes.rs b/packages/native-core/tests/change_nodes.rs index bb15fd47d..387481462 100644 --- a/packages/native-core/tests/change_nodes.rs +++ b/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]); diff --git a/packages/native-core/tests/initial_build.rs b/packages/native-core/tests/initial_build.rs index 74bc809c7..d17ea05d4 100644 --- a/packages/native-core/tests/initial_build.rs +++ b/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 { diff --git a/packages/native-core/tests/state.rs b/packages/native-core/tests/state.rs index e738dc70d..a31110f18 100644 --- a/packages/native-core/tests/state.rs +++ b/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 = ClientTree::new(); + let mut tree: RealDom = 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 = ClientTree::new(); + let mut tree: RealDom = 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 = ClientTree::new(); + let mut tree: RealDom = 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 = ClientTree::new(); + let mut tree: RealDom = 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 }); }); } diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 866d1fc85..3813b97ae 100644 --- a/packages/tui/src/hooks.rs +++ b/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, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut ClientTree, + tree: &mut RealDom, ) { let previous_mouse = self .mouse @@ -194,7 +192,7 @@ impl InnerInputState { previous_mouse: Option<(MouseData, Vec)>, resolved_events: &mut Vec, layout: &Stretch, - tree: &mut ClientTree, + tree: &mut RealDom, ) { struct Data<'b> { new_pos: (i32, i32), @@ -219,7 +217,7 @@ impl InnerInputState { will_bubble: &mut FxHashSet, resolved_events: &mut Vec, node: &TreeNode, - tree: &ClientTree, + tree: &RealDom, ) { // 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, + tree: &mut RealDom, ) -> Vec { let mut resolved_events = Vec::new(); diff --git a/packages/native-core/src/layout.rs b/packages/tui/src/layout.rs similarity index 84% rename from packages/native-core/src/layout.rs rename to packages/tui/src/layout.rs index 5bfdb238b..9d9be8f6a 100644 --- a/packages/native-core/src/layout.rs +++ b/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, diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index 0fda78071..4c869d1f9 100644 --- a/packages/tui/src/lib.rs +++ b/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, +} +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); - cx.provide_root_context(state); + // 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; + } + } + } + }); - let mut tree: ClientTree = ClientTree::new(); + cx.provide_root_context(state); + cx.provide_root_context(TuiContext { tx: event_tx_clone }); + + let mut tree: RealDom = 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, + crossterm_event_sender: UnboundedSender, + mut event_reciever: UnboundedReceiver, handler: RinkInputHandler, cfg: Config, - mut tree: ClientTree, + mut tree: RealDom, 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, } diff --git a/packages/tui/src/render.rs b/packages/tui/src/render.rs index fe21ba8ae..9e4310be4 100644 --- a/packages/tui/src/render.rs +++ b/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>, layout: &Stretch, - tree: &ClientTree, + tree: &RealDom, node: &TreeNode, 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); diff --git a/packages/tui/src/style.rs b/packages/tui/src/style.rs index 823a94725..857322079 100644 --- a/packages/tui/src/style.rs +++ b/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, diff --git a/packages/tui/src/style_attributes.rs b/packages/tui/src/style_attributes.rs index 90cebe47d..6d0bb9672 100644 --- a/packages/tui/src/style_attributes.rs +++ b/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};