From c884f8c1b850230114e390af0c057b28d090429c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 1 Jan 2022 09:49:08 -0500 Subject: [PATCH] feat: add full async suport --- Cargo.toml | 2 + README.md | 1 - examples/frame.rs | 2 +- examples/list.rs | 2 +- examples/margin.rs | 2 +- examples/quadrants.rs | 2 +- examples/readme.rs | 2 +- examples/task.rs | 51 +++++++++++ examples/text.rs | 2 +- src/lib.rs | 191 ++++++++++++++++++++++++------------------ 10 files changed, 168 insertions(+), 89 deletions(-) create mode 100644 examples/task.rs diff --git a/Cargo.toml b/Cargo.toml index 7672c10d6..2b67f00c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,6 @@ hecs = "0.7.3" ctrlc = "3.2.1" bumpalo = { version = "3.8.0", features = ["boxed"] } stretch2 = { path = "../../Tinkering/stretch2" } +tokio = { version = "1.15.0", features = ["full"] } +futures = "0.3.19" # stretch2 = { git = "https://github.com/elbaro/stretch2.git" } diff --git a/README.md b/README.md index c1202b659..064a262cf 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,6 @@ static App: FC<()> = |cx| { justify_content: "center", align_items: "center", - "Hello world!" } }) diff --git a/examples/frame.rs b/examples/frame.rs index 3b913b9c9..20bbbfb36 100644 --- a/examples/frame.rs +++ b/examples/frame.rs @@ -4,7 +4,7 @@ fn main() { let mut dom = VirtualDom::new(app); dom.rebuild(); - rink::render_vdom(&dom).unwrap(); + rink::render_vdom(&mut dom).unwrap(); } fn app(cx: Scope) -> Element { diff --git a/examples/list.rs b/examples/list.rs index ebdb68e3c..354f3f008 100644 --- a/examples/list.rs +++ b/examples/list.rs @@ -4,7 +4,7 @@ fn main() { let mut dom = VirtualDom::new(app); dom.rebuild(); - rink::render_vdom(&dom).unwrap(); + rink::render_vdom(&mut dom).unwrap(); } fn app(cx: Scope) -> Element { diff --git a/examples/margin.rs b/examples/margin.rs index db08ff802..853721027 100644 --- a/examples/margin.rs +++ b/examples/margin.rs @@ -4,7 +4,7 @@ fn main() { let mut dom = VirtualDom::new(app); dom.rebuild(); - rink::render_vdom(&dom).unwrap(); + rink::render_vdom(&mut dom).unwrap(); } fn app(cx: Scope) -> Element { diff --git a/examples/quadrants.rs b/examples/quadrants.rs index 88138eeb7..4e9642b6e 100644 --- a/examples/quadrants.rs +++ b/examples/quadrants.rs @@ -4,7 +4,7 @@ fn main() { let mut dom = VirtualDom::new(app); dom.rebuild(); - rink::render_vdom(&dom).unwrap(); + rink::render_vdom(&mut dom).unwrap(); } fn app(cx: Scope) -> Element { diff --git a/examples/readme.rs b/examples/readme.rs index 2edb2ac98..7f68e884c 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -4,7 +4,7 @@ fn main() { let mut dom = VirtualDom::new(app); dom.rebuild(); - rink::render_vdom(&dom).unwrap(); + rink::render_vdom(&mut dom).unwrap(); } fn app(cx: Scope) -> Element { diff --git a/examples/task.rs b/examples/task.rs new file mode 100644 index 000000000..f8a8fe111 --- /dev/null +++ b/examples/task.rs @@ -0,0 +1,51 @@ +use dioxus::prelude::*; + +fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + rink::render_vdom(&mut dom).unwrap(); +} + +fn app(cx: Scope) -> Element { + let count = use_state(&cx, || 0); + + use_future(&cx, || { + let set_count = count.setter(); + let mut mycount = 0; + let update = cx.schedule_update(); + async move { + loop { + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + mycount += 1; + set_count(mycount); + update(); + } + } + }); + + cx.render(rsx! { + div { width: "100%", + div { width: "50%", height: "5px", background_color: "blue", justify_content: "center", align_items: "center", + "Hello {count}!" + } + div { width: "50%", height: "10px", background_color: "red", justify_content: "center", align_items: "center", + "Hello {count}!" + } + } + }) +} + +// use_future(&cx, || { +// let set_count = count.setter(); +// let mut mycount = 0; +// let update = cx.schedule_update(); +// async move { +// loop { +// tokio::time::sleep(std::time::Duration::from_millis(100)).await; +// mycount += 1; +// set_count(mycount); +// update(); +// } +// } +// }); diff --git a/examples/text.rs b/examples/text.rs index 3b913b9c9..20bbbfb36 100644 --- a/examples/text.rs +++ b/examples/text.rs @@ -4,7 +4,7 @@ fn main() { let mut dom = VirtualDom::new(app); dom.rebuild(); - rink::render_vdom(&dom).unwrap(); + rink::render_vdom(&mut dom).unwrap(); } fn app(cx: Scope) -> Element { diff --git a/src/lib.rs b/src/lib.rs index 9acb3d487..6b737538d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,15 @@ use anyhow::Result; use crossterm::{ - event::{self, DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent}, + event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyEvent}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; +use dioxus::core::exports::futures_channel::mpsc::unbounded; use dioxus::core::*; +use futures::{future::Either, pin_mut, StreamExt}; use std::{ collections::HashMap, io, - sync::mpsc, time::{Duration, Instant}, }; use stretch2::{prelude::Size, Stretch}; @@ -28,22 +29,10 @@ pub struct TuiNode<'a> { pub node: &'a VNode<'a>, } -pub fn render_vdom(vdom: &VirtualDom) -> Result<()> { - /* - Get the terminal to calcualte the layout from - */ - enable_raw_mode().unwrap(); - ctrlc::set_handler(move || { - disable_raw_mode().unwrap(); - }) - .expect("Error setting Ctrl-C handler"); - let mut stdout = std::io::stdout(); - execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap(); - let backend = CrosstermBackend::new(io::stdout()); - let mut terminal = Terminal::new(backend).unwrap(); - +pub fn render_vdom(vdom: &mut VirtualDom) -> Result<()> { // Setup input handling - let (tx, rx) = mpsc::channel(); + let (tx, mut rx) = unbounded(); + std::thread::spawn(move || { let tick_rate = Duration::from_millis(100); let mut last_tick = Instant::now(); @@ -53,93 +42,131 @@ pub fn render_vdom(vdom: &VirtualDom) -> Result<()> { .checked_sub(last_tick.elapsed()) .unwrap_or_else(|| Duration::from_secs(0)); - if event::poll(timeout).unwrap() { - if let TermEvent::Key(key) = event::read().unwrap() { - tx.send(InputEvent::UserInput(key)).unwrap(); - } + 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.send(InputEvent::Tick).unwrap(); + tx.unbounded_send(InputEvent::Tick).unwrap(); last_tick = Instant::now(); } } }); - terminal.clear().unwrap(); + 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(); + let backend = CrosstermBackend::new(io::stdout()); + let mut terminal = Terminal::new(backend).unwrap(); - loop { - let dims = terminal.size().unwrap(); - let width = dims.width; - let height = dims.height; + terminal.clear().unwrap(); - /* - -> collect all the nodes with their layout - -> solve their layout - -> render the nodes in the right place with tui/crosstream - -> while rendering, apply styling + loop { + let dims = terminal.size().unwrap(); + let width = dims.width; + let height = dims.height; - use simd to compare lines for diffing? + /* + -> collect all the nodes with their layout + -> solve their layout + -> render the nodes in the right place with tui/crosstream + -> while rendering, apply styling - */ - let mut layout = Stretch::new(); - let mut nodes = HashMap::new(); + use simd to compare lines for diffing? - let root_node = vdom.base_scope().root_node(); - layout::collect_layout(&mut layout, &mut nodes, vdom, root_node); - /* - Compute the layout given th terminal size - */ - let node_id = root_node.try_mounted_id().unwrap(); - let root_layout = nodes[&node_id].layout; - layout.compute_layout( - root_layout, - Size { - width: stretch2::prelude::Number::Defined(width as f32), - height: stretch2::prelude::Number::Defined(height as f32), - }, - )?; - terminal.draw(|frame| { - // - render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); - assert!(nodes.is_empty()); - })?; + todo: reuse the layout and node objects. + our work_with_deadline method can tell us which nodes are dirty. + */ + let mut layout = Stretch::new(); + let mut nodes = HashMap::new(); - match rx.recv()? { - InputEvent::UserInput(event) => match event.code { - KeyCode::Char('q') => { - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; - break; + let root_node = vdom.base_scope().root_node(); + layout::collect_layout(&mut layout, &mut nodes, vdom, root_node); + /* + Compute the layout given th terminal size + */ + let node_id = root_node.try_mounted_id().unwrap(); + let root_layout = nodes[&node_id].layout; + layout.compute_layout( + root_layout, + Size { + width: stretch2::prelude::Number::Defined(width as f32), + height: stretch2::prelude::Number::Defined(height as f32), + }, + )?; + + terminal.draw(|frame| { + // + render::render_vnode(frame, &layout, &mut nodes, vdom, root_node); + assert!(nodes.is_empty()); + })?; + + use futures::future::{select, Either}; + { + let wait = vdom.wait_for_work(); + pin_mut!(wait); + + match select(wait, rx.next()).await { + Either::Left((a, b)) => { + // println!("work"); + } + Either::Right((evt, o)) => { + // println!("event"); + match evt.unwrap() { + InputEvent::UserInput(event) => match event { + TermEvent::Key(key) => { + match key.code { + KeyCode::Char('q') => { + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; + break; + } + _ => {} // handle event + } + } + TermEvent::Mouse(_) => {} + TermEvent::Resize(_, _) => {} + }, + InputEvent::Tick => {} // tick + InputEvent::Close => { + break; + } + }; + } + } } - _ => {} // handle event - }, - InputEvent::Tick => {} // tick - InputEvent::Close => { - break; + + vdom.work_with_deadline(|| false); } - }; - } - disable_raw_mode()?; - execute!( - terminal.backend_mut(), - LeaveAlternateScreen, - DisableMouseCapture - )?; - terminal.show_cursor()?; + disable_raw_mode()?; + execute!( + terminal.backend_mut(), + LeaveAlternateScreen, + DisableMouseCapture + )?; + terminal.show_cursor()?; - Ok(()) + Ok(()) + }) } enum InputEvent { - UserInput(KeyEvent), + UserInput(TermEvent), Close, Tick, }