mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
add benchmark
This commit is contained in:
parent
366a0a8026
commit
c8919ad77b
5 changed files with 650 additions and 60 deletions
|
@ -88,3 +88,7 @@ harness = false
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "jsframework"
|
name = "jsframework"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "tui_update"
|
||||||
|
harness = false
|
268
benches/tui_update.rs
Normal file
268
benches/tui_update.rs
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_tui::{Config, TuiContext};
|
||||||
|
|
||||||
|
criterion_group!(mbenches, tui_update);
|
||||||
|
criterion_main!(mbenches);
|
||||||
|
|
||||||
|
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
|
||||||
|
fn tui_update(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Update boxes");
|
||||||
|
|
||||||
|
// We can also use loops to define multiple benchmarks, even over multiple dimensions.
|
||||||
|
for size in 1..=8u32 {
|
||||||
|
let parameter_string = format!("{}", (3 * size).pow(2));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("size", parameter_string),
|
||||||
|
&size,
|
||||||
|
|b, size| {
|
||||||
|
b.iter(|| match size {
|
||||||
|
1 => dioxus::tui::launch_cfg(
|
||||||
|
app3,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
2 => dioxus::tui::launch_cfg(
|
||||||
|
app6,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
3 => dioxus::tui::launch_cfg(
|
||||||
|
app9,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
4 => dioxus::tui::launch_cfg(
|
||||||
|
app12,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
5 => dioxus::tui::launch_cfg(
|
||||||
|
app15,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
6 => dioxus::tui::launch_cfg(
|
||||||
|
app18,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
7 => dioxus::tui::launch_cfg(
|
||||||
|
app21,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
8 => dioxus::tui::launch_cfg(
|
||||||
|
app24,
|
||||||
|
Config {
|
||||||
|
headless: true,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => (),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 app3(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app6(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app9(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app12(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app15(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 15,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app18(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app21(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 21,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app24(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
260
examples/tui_stress_test.rs
Normal file
260
examples/tui_stress_test.rs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_tui::{Config, TuiContext};
|
||||||
|
|
||||||
|
criterion_group!(mbenches, tui_update);
|
||||||
|
criterion_main!(mbenches);
|
||||||
|
|
||||||
|
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
|
||||||
|
fn tui_update(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("Update boxes");
|
||||||
|
|
||||||
|
// We can also use loops to define multiple benchmarks, even over multiple dimensions.
|
||||||
|
for size in 1..=8u32 {
|
||||||
|
let parameter_string = format!("{}", (3 * size).pow(2));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("size", parameter_string),
|
||||||
|
&size,
|
||||||
|
|b, size| {
|
||||||
|
b.iter(|| match size {
|
||||||
|
1 => dioxus::tui::launch_cfg(
|
||||||
|
app3,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
2 => dioxus::tui::launch_cfg(
|
||||||
|
app6,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
3 => dioxus::tui::launch_cfg(
|
||||||
|
app9,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
4 => dioxus::tui::launch_cfg(
|
||||||
|
app12,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
5 => dioxus::tui::launch_cfg(
|
||||||
|
app15,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
6 => dioxus::tui::launch_cfg(
|
||||||
|
app18,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
7 => dioxus::tui::launch_cfg(
|
||||||
|
app21,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
8 => dioxus::tui::launch_cfg(
|
||||||
|
app24,
|
||||||
|
Config {
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => (),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 app3(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app6(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app9(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 9,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app12(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app15(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 15,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app18(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 18,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app21(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 21,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app24(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
Grid{
|
||||||
|
size: 24,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,6 +1,21 @@
|
||||||
#[derive(Default, Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub rendering_mode: RenderingMode,
|
pub rendering_mode: RenderingMode,
|
||||||
|
/// Controls if the terminal quit when the user presses `ctrl+c`?
|
||||||
|
/// To handle quiting on your own, use the [crate::TuiContext] root context.
|
||||||
|
pub ctrl_c_quit: bool,
|
||||||
|
/// Controls if the terminal should dislay anything, usefull for testing.
|
||||||
|
pub headless: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rendering_mode: Default::default(),
|
||||||
|
ctrl_c_quit: true,
|
||||||
|
headless: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
|
|
@ -6,13 +6,19 @@ use crossterm::{
|
||||||
};
|
};
|
||||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
|
use futures::{
|
||||||
|
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||||
|
pin_mut, StreamExt,
|
||||||
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io,
|
io,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use stretch2::{prelude::Size, Stretch};
|
use stretch2::{
|
||||||
|
prelude::{Node, Size},
|
||||||
|
Stretch,
|
||||||
|
};
|
||||||
use style::RinkStyle;
|
use style::RinkStyle;
|
||||||
use tui::{backend::CrosstermBackend, Terminal};
|
use tui::{backend::CrosstermBackend, Terminal};
|
||||||
|
|
||||||
|
@ -30,6 +36,16 @@ pub use hooks::*;
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
pub use render::*;
|
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<()>) {
|
pub fn launch(app: Component<()>) {
|
||||||
launch_cfg(app, Config::default())
|
launch_cfg(app, Config::default())
|
||||||
}
|
}
|
||||||
|
@ -37,8 +53,34 @@ pub fn launch(app: Component<()>) {
|
||||||
pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
let (tx, rx) = unbounded();
|
let (tx, rx) = unbounded();
|
||||||
|
// Setup input handling
|
||||||
|
let (event_tx, event_rx) = unbounded();
|
||||||
|
let event_tx_clone = event_tx.clone();
|
||||||
|
if !cfg.headless {
|
||||||
|
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();
|
||||||
|
event_tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if last_tick.elapsed() >= tick_rate {
|
||||||
|
event_tx.unbounded_send(InputEvent::Tick).unwrap();
|
||||||
|
last_tick = Instant::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let cx = dom.base_scope();
|
let cx = dom.base_scope();
|
||||||
|
cx.provide_root_context(TuiContext { tx: event_tx_clone });
|
||||||
|
|
||||||
let (handler, state) = RinkInputHandler::new(rx, cx);
|
let (handler, state) = RinkInputHandler::new(rx, cx);
|
||||||
|
|
||||||
|
@ -46,7 +88,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||||
|
|
||||||
dom.rebuild();
|
dom.rebuild();
|
||||||
|
|
||||||
render_vdom(&mut dom, tx, handler, cfg).unwrap();
|
render_vdom(&mut dom, event_rx, tx, handler, cfg).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TuiNode<'a> {
|
pub struct TuiNode<'a> {
|
||||||
|
@ -56,35 +98,13 @@ pub struct TuiNode<'a> {
|
||||||
pub node: &'a VNode<'a>,
|
pub node: &'a VNode<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_vdom(
|
fn render_vdom(
|
||||||
vdom: &mut VirtualDom,
|
vdom: &mut VirtualDom,
|
||||||
|
mut event_reciever: UnboundedReceiver<InputEvent>,
|
||||||
ctx: UnboundedSender<TermEvent>,
|
ctx: UnboundedSender<TermEvent>,
|
||||||
handler: RinkInputHandler,
|
handler: RinkInputHandler,
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
) -> Result<()> {
|
) -> 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()
|
tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
.enable_all()
|
||||||
.build()?
|
.build()?
|
||||||
|
@ -92,13 +112,17 @@ pub fn render_vdom(
|
||||||
/*
|
/*
|
||||||
Get the terminal to calcualte the layout from
|
Get the terminal to calcualte the layout from
|
||||||
*/
|
*/
|
||||||
enable_raw_mode().unwrap();
|
let mut terminal = (!cfg.headless).then(|| {
|
||||||
let mut stdout = std::io::stdout();
|
enable_raw_mode().unwrap();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
|
let mut stdout = std::io::stdout();
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
|
Terminal::new(backend).unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
terminal.clear().unwrap();
|
if let Some(terminal) = &mut terminal {
|
||||||
|
terminal.clear().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
/*
|
/*
|
||||||
|
@ -126,34 +150,51 @@ pub fn render_vdom(
|
||||||
let root_layout = nodes[&node_id].layout;
|
let root_layout = nodes[&node_id].layout;
|
||||||
let mut events = Vec::new();
|
let mut events = Vec::new();
|
||||||
|
|
||||||
terminal.draw(|frame| {
|
fn resize(dims: tui::layout::Rect, stretch: &mut Stretch, root_layout: Node) {
|
||||||
// size is guaranteed to not change when rendering
|
|
||||||
let dims = frame.size();
|
|
||||||
let width = dims.width;
|
let width = dims.width;
|
||||||
let height = dims.height;
|
let height = dims.height;
|
||||||
layout
|
|
||||||
|
stretch
|
||||||
.compute_layout(
|
.compute_layout(
|
||||||
root_layout,
|
root_layout,
|
||||||
Size {
|
Size {
|
||||||
width: stretch2::prelude::Number::Defined(width as f32),
|
width: stretch2::prelude::Number::Defined((width - 1) as f32),
|
||||||
height: stretch2::prelude::Number::Defined(height as f32),
|
height: stretch2::prelude::Number::Defined((height - 1) as f32),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
// resolve events before rendering
|
if let Some(terminal) = &mut terminal {
|
||||||
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
|
terminal.draw(|frame| {
|
||||||
render::render_vnode(
|
// size is guaranteed to not change when rendering
|
||||||
frame,
|
resize(frame.size(), &mut layout, root_layout);
|
||||||
&layout,
|
|
||||||
&mut nodes,
|
// resolve events before rendering
|
||||||
vdom,
|
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
|
||||||
root_node,
|
render::render_vnode(
|
||||||
&RinkStyle::default(),
|
frame,
|
||||||
cfg,
|
&layout,
|
||||||
|
&mut nodes,
|
||||||
|
vdom,
|
||||||
|
root_node,
|
||||||
|
&RinkStyle::default(),
|
||||||
|
cfg,
|
||||||
|
);
|
||||||
|
assert!(nodes.is_empty());
|
||||||
|
})?;
|
||||||
|
} else {
|
||||||
|
resize(
|
||||||
|
tui::layout::Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
},
|
||||||
|
&mut layout,
|
||||||
|
root_layout,
|
||||||
);
|
);
|
||||||
assert!(nodes.is_empty());
|
}
|
||||||
})?;
|
|
||||||
|
|
||||||
for e in events {
|
for e in events {
|
||||||
vdom.handle_message(SchedulerMsg::Event(e));
|
vdom.handle_message(SchedulerMsg::Event(e));
|
||||||
|
@ -164,7 +205,7 @@ pub fn render_vdom(
|
||||||
let wait = vdom.wait_for_work();
|
let wait = vdom.wait_for_work();
|
||||||
pin_mut!(wait);
|
pin_mut!(wait);
|
||||||
|
|
||||||
match select(wait, rx.next()).await {
|
match select(wait, event_reciever.next()).await {
|
||||||
Either::Left((_a, _b)) => {
|
Either::Left((_a, _b)) => {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -194,13 +235,15 @@ pub fn render_vdom(
|
||||||
vdom.work_with_deadline(|| false);
|
vdom.work_with_deadline(|| false);
|
||||||
}
|
}
|
||||||
|
|
||||||
disable_raw_mode()?;
|
if let Some(terminal) = &mut terminal {
|
||||||
execute!(
|
disable_raw_mode()?;
|
||||||
terminal.backend_mut(),
|
execute!(
|
||||||
LeaveAlternateScreen,
|
terminal.backend_mut(),
|
||||||
DisableMouseCapture
|
LeaveAlternateScreen,
|
||||||
)?;
|
DisableMouseCapture
|
||||||
terminal.show_cursor()?;
|
)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue