Engine structure

This commit is contained in:
2026-01-28 10:27:18 +01:00
parent 665b502a09
commit 011b1ab963
6 changed files with 178 additions and 93 deletions
+15
View File
@@ -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<Context>) -> View {
rsx! {
"Hello, world!"
}
.view(cx.clone())
}
+3 -56
View File
@@ -1,68 +1,15 @@
use std::sync::Arc;
use osui::{prelude::*, rsx};
use osui::prelude::*;
pub struct Count(pub State<u16>);
fn main() {
pub fn main() {
let console = Console::new();
console.run(app);
}
fn app(cx: &Arc<Context>) -> 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<Context>) -> 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())
}
+90
View File
@@ -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: Engine>(T);
impl<T: Engine> Benchmark<T> {
pub fn new(engine: T) -> Self {
Self(engine)
}
}
impl<T: Engine> Engine for Benchmark<T> {
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
let mut times: Vec<usize> = 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::<usize>() / 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<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
&self,
component: F,
) -> Arc<Context> {
self.0.init(component)
}
fn draw_context(&self, ctx: &DrawContext) {
self.0.draw_context(ctx)
}
fn render(&self, cx: &Arc<Context>) {
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();
}
}
+37 -37
View File
@@ -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<Vec<Arc<dyn Fn(Arc<Context>) + Send + Sync>>>,
@@ -26,39 +23,6 @@ impl Console {
pub fn thread<F: Fn(Arc<Context>) + Send + Sync + 'static>(&self, run: F) {
self.threads.lock().unwrap().push(Arc::new(run));
}
pub fn run<F: Fn(&Arc<Context>) -> 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<Context>) {
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<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
&self,
component: F,
) -> Arc<Context> {
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
}
}
+32
View File
@@ -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<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
let cx = self.init(component);
loop {
self.render(&cx);
self.render_delay();
}
}
fn init<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
&self,
component: F,
) -> Arc<Context>;
fn render(&self, cx: &Arc<Context>);
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);
}
+1
View File
@@ -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;
}