diff --git a/examples/benchmark.rs b/examples/benchmark.rs new file mode 100644 index 0000000..4fc8e72 --- /dev/null +++ b/examples/benchmark.rs @@ -0,0 +1,15 @@ +use std::sync::Arc; + +use osui::prelude::*; + +pub fn main() { + let engine = Benchmark::new(Console::new()); + engine.run(app); +} + +fn app(cx: &Arc) -> View { + rsx! { + "Hello, world!" + } + .view(cx.clone()) +} diff --git a/examples/hello_world.rs b/examples/hello_world.rs index 09c9dd3..9ba9489 100644 --- a/examples/hello_world.rs +++ b/examples/hello_world.rs @@ -1,68 +1,15 @@ use std::sync::Arc; -use osui::{prelude::*, rsx}; +use osui::prelude::*; -pub struct Count(pub State); - -fn main() { +pub fn main() { let console = Console::new(); console.run(app); } fn app(cx: &Arc) -> View { - let count = use_state(0); - let mount = use_mount_manual(); - - // Sync state with children - use_sync_effect(cx, &count, |v| Count(v.clone()), &[&mount, &count]); - - use_effect( - { - let count = count.clone(); - move || loop { - std::thread::sleep(std::time::Duration::from_millis(500)); - let mut count = count.get(); - if *count > 5 { - *count = 0; - continue; - } - *count += 1; - } - }, - &[&mount], - ); - rsx! { - // Static scope - my_component (ctx, view) { - let area = ctx.allocate(5, 0, 10, 10); - ctx.draw_view(area, view); - } - - // Dynamic scope - if %count (*count.get() > 5) { - my_component (ctx, view) { - let area = ctx.allocate(18, 0, 10, 10); - ctx.draw_view(area, view); - } - } - - // Dynamic scope - for %count (i in 0..count.get_dl()) { - "{i}" @Point { x: 0, y: i }; - } - - !mount mount - } - .view(cx.clone()) -} - -fn my_component(cx: &Arc) -> View { - // Sync state with parent - let count = use_sync_state(cx, 0, |Count(v)| v.get_dl()); - - rsx! { - "Count: {count}" + "Hello, world!" } .view(cx.clone()) } diff --git a/src/engine/benchmark.rs b/src/engine/benchmark.rs new file mode 100644 index 0000000..d3ed0a1 --- /dev/null +++ b/src/engine/benchmark.rs @@ -0,0 +1,90 @@ +use std::{io::stdout, sync::Arc, time::Instant}; + +use crossterm::{cursor::MoveTo, execute, terminal::Clear}; + +use crate::{prelude::Context, render::Area, DrawContext, View}; + +use super::Engine; + +pub struct Benchmark(T); + +impl Benchmark { + pub fn new(engine: T) -> Self { + Self(engine) + } +} + +impl Engine for Benchmark { + fn run) -> View + Send + Sync + 'static>(&self, component: F) { + let mut times: Vec = Vec::new(); + let cx = self.init(component); + + let start = Instant::now(); + + for _ in 0..100 { + let start = Instant::now(); + self.render(&cx); + let end = Instant::now(); + times.push(end.duration_since(start).as_millis() as usize); + self.0.render_delay(); + } + + let end = Instant::now(); + + execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap(); + execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap(); + execute!(stdout(), MoveTo(0, 0)).unwrap(); + + println!( + "Average time: {}ms", + if times.len() > 0 { + times.iter().sum::() / times.len() + } else { + 0 + } + ); + println!("Min time: {}ms", times.iter().min().unwrap_or(&0)); + println!("Max time: {}ms", times.iter().max().unwrap_or(&0)); + println!( + "Median time: {}ms", + times.iter().nth(times.len() / 2).unwrap_or(&0) + ); + println!( + "90th percentile: {}ms", + times.iter().nth(times.len() * 90 / 100).unwrap_or(&0) + ); + println!( + "95th percentile: {}ms", + times.iter().nth(times.len() * 95 / 100).unwrap_or(&0) + ); + println!( + "99th percentile: {}ms", + times.iter().nth(times.len() * 99 / 100).unwrap_or(&0) + ); + + println!("Total time: {}ms", end.duration_since(start).as_millis()); + } + + fn init) -> View + Send + Sync + 'static>( + &self, + component: F, + ) -> Arc { + self.0.init(component) + } + + fn draw_context(&self, ctx: &DrawContext) { + self.0.draw_context(ctx) + } + + fn render(&self, cx: &Arc) { + self.0.render(cx) + } + + fn render_view(&self, area: &Area, view: &View) -> DrawContext { + self.0.render_view(area, view) + } + + fn render_delay(&self) { + self.0.render_delay(); + } +} diff --git a/src/engine.rs b/src/engine/console.rs similarity index 68% rename from src/engine.rs rename to src/engine/console.rs index ac02d84..ddf4d21 100644 --- a/src/engine.rs +++ b/src/engine/console.rs @@ -7,10 +7,7 @@ use crossterm::{cursor::MoveTo, execute, terminal::Clear}; use crate::{prelude::Context, render::Area, DrawContext, View}; -pub trait Engine { - fn render_view(&self, area: &Area, view: &View) -> DrawContext; - fn draw_context(&self, ctx: &DrawContext); -} +use super::Engine; pub struct Console { threads: Mutex) + Send + Sync>>>, @@ -26,39 +23,6 @@ impl Console { pub fn thread) + Send + Sync + 'static>(&self, run: F) { self.threads.lock().unwrap().push(Arc::new(run)); } - - pub fn run) -> View + Send + Sync + 'static>(&self, component: F) { - let cx = Context::new(component); - cx.refresh(); - - for thread in self.threads.lock().unwrap().iter() { - let thread = thread.clone(); - - std::thread::spawn({ - let cx = cx.clone(); - move || thread(cx) - }); - } - - loop { - let (width, height) = crossterm::terminal::size().unwrap(); - - execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap(); - execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap(); - - self.draw_context(&self.render_view( - &Area { - x: 0, - y: 0, - width, - height, - }, - &cx.get_view(), - )); - - std::thread::sleep(std::time::Duration::from_millis(16)); - } - } } impl Engine for Console { @@ -84,4 +48,40 @@ impl Engine for Console { } } } + + fn render(&self, cx: &Arc) { + let (width, height) = crossterm::terminal::size().unwrap(); + + execute!(stdout(), Clear(crossterm::terminal::ClearType::Purge)).unwrap(); + execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap(); + + self.draw_context(&self.render_view( + &Area { + x: 0, + y: 0, + width, + height, + }, + &cx.get_view(), + )); + } + + fn init) -> View + Send + Sync + 'static>( + &self, + component: F, + ) -> Arc { + let cx = Context::new(component); + cx.refresh(); + + for thread in self.threads.lock().unwrap().iter() { + let thread = thread.clone(); + + std::thread::spawn({ + let cx = cx.clone(); + move || thread(cx) + }); + } + + cx + } } diff --git a/src/engine/mod.rs b/src/engine/mod.rs new file mode 100644 index 0000000..0385182 --- /dev/null +++ b/src/engine/mod.rs @@ -0,0 +1,32 @@ +pub mod benchmark; +pub mod console; + +pub use benchmark::*; +pub use console::*; + +use std::sync::Arc; + +use crate::{prelude::Context, render::Area, DrawContext, View}; + +pub trait Engine { + fn run) -> View + Send + Sync + 'static>(&self, component: F) { + let cx = self.init(component); + + loop { + self.render(&cx); + self.render_delay(); + } + } + + fn init) -> View + Send + Sync + 'static>( + &self, + component: F, + ) -> Arc; + fn render(&self, cx: &Arc); + fn render_delay(&self) { + std::thread::sleep(std::time::Duration::from_millis(16)) + } + + fn render_view(&self, area: &Area, view: &View) -> DrawContext; + fn draw_context(&self, ctx: &DrawContext); +} diff --git a/src/lib.rs b/src/lib.rs index 1462c02..690fac9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod prelude { pub use crate::component::*; pub use crate::engine::*; pub use crate::render::*; + pub use crate::rsx; pub use crate::state::*; pub use crate::View; }