From 011b1ab96349de742bd344ba9af61bb1877dbd72 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 10:27:18 +0100 Subject: [PATCH 1/7] Engine structure --- examples/benchmark.rs | 15 +++++ examples/hello_world.rs | 59 +----------------- src/engine/benchmark.rs | 90 ++++++++++++++++++++++++++++ src/{engine.rs => engine/console.rs} | 74 +++++++++++------------ src/engine/mod.rs | 32 ++++++++++ src/lib.rs | 1 + 6 files changed, 178 insertions(+), 93 deletions(-) create mode 100644 examples/benchmark.rs create mode 100644 src/engine/benchmark.rs rename src/{engine.rs => engine/console.rs} (68%) create mode 100644 src/engine/mod.rs 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; } From 46587ff8223a6072549be7265dfa29745a95e333 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 15:21:45 +0100 Subject: [PATCH 2/7] rsx proc macro --- Cargo.toml | 4 ++ macros/Cargo.toml | 13 ++++ macros/src/emit.rs | 98 ++++++++++++++++++++++++++ macros/src/lib.rs | 10 +++ macros/src/parse.rs | 129 ++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/macros.rs | 165 -------------------------------------------- 7 files changed, 255 insertions(+), 167 deletions(-) create mode 100644 macros/Cargo.toml create mode 100644 macros/src/emit.rs create mode 100644 macros/src/lib.rs create mode 100644 macros/src/parse.rs delete mode 100644 src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index a40db29..6aec650 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ cc = "1.0" [dependencies] crossterm = "0.28.1" figlet-rs = "0.1.5" +osui-macros = { path = "macros" } [profile.dev] opt-level = 1 @@ -42,3 +43,6 @@ overflow-checks = false lto = true panic = 'abort' codegen-units = 1 + +[workspace] +members = [".", "macros"] diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..3337994 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "osui-macros" +version = "0.1.0" +edition = "2024" +publish = false + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2", features = ["full"] } +quote = "1" +proc-macro2 = "1" diff --git a/macros/src/emit.rs b/macros/src/emit.rs new file mode 100644 index 0000000..d909d2a --- /dev/null +++ b/macros/src/emit.rs @@ -0,0 +1,98 @@ +use crate::parse::*; +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; + +pub fn emit_rsx(root: RsxRoot) -> TokenStream { + let nodes = root.nodes.iter().map(emit_node); + + quote! {{ + let mut r = osui::frontend::Rsx::new(); + #(#nodes)* + r + }} +} + +fn emit_deps(deps: &[Dep]) -> TokenStream { + deps.iter() + .map(|d| { + let ident = &d.ident; + if let Some(pat) = &d.pat { + quote!( let #pat = #ident.clone(); ) + } else { + quote!( let #ident = #ident.clone(); ) + } + }) + .collect() +} + +fn emit_node(node: &RsxNode) -> TokenStream { + match node { + RsxNode::Text(text) => quote! { + { + let scope = osui::component::Scope::new(); + scope.view(Arc::new(move |ctx| { + ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text)) + })); + r.static_scope(scope); + } + }, + + RsxNode::Component(name) => quote! { + { + let scope = osui::component::Scope::new(); + scope.child(#name, None); + r.static_scope(scope); + } + }, + + RsxNode::Mount(m) => quote! { + #m.mount(); + }, + + RsxNode::If { + deps, + cond, + children, + } => { + let deps_emit = emit_deps(deps); + let kids = children.iter().map(emit_node); + + quote! { + { + #deps_emit + r.dynamic_scope(move |scope| { + if #cond { + if scope.children.lock().unwrap().is_empty() { + #(#kids)* + } + } else { + scope.children.lock().unwrap().clear(); + } + }); + } + } + } + + RsxNode::For { + deps, + pat, + expr, + children, + } => { + let deps_emit = emit_deps(deps); + let kids = children.iter().map(emit_node); + + quote! { + { + #deps_emit + r.dynamic_scope(move |scope| { + scope.children.lock().unwrap().clear(); + for #pat in #expr { + #(#kids)* + } + }); + } + } + } + } +} diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..bb4e672 --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,10 @@ +use proc_macro::TokenStream; + +mod emit; +mod parse; + +#[proc_macro] +pub fn rsx(input: TokenStream) -> TokenStream { + let ast = syn::parse_macro_input!(input as parse::RsxRoot); + emit::emit_rsx(ast).into() +} diff --git a/macros/src/parse.rs b/macros/src/parse.rs new file mode 100644 index 0000000..fc4ee19 --- /dev/null +++ b/macros/src/parse.rs @@ -0,0 +1,129 @@ +use syn::braced; +use syn::{ + Expr, Ident, LitStr, Pat, Result, Token, + parse::{Parse, ParseStream}, +}; + +pub struct RsxRoot { + pub nodes: Vec, +} + +impl Parse for RsxRoot { + fn parse(input: ParseStream) -> Result { + let mut nodes = Vec::new(); + while !input.is_empty() { + nodes.push(input.parse()?); + } + Ok(Self { nodes }) + } +} + +pub enum RsxNode { + Text(LitStr), + Component(Ident), + Mount(Ident), + If { + deps: Vec, + cond: Expr, + children: Vec, + }, + For { + deps: Vec, + pat: Pat, + expr: Expr, + children: Vec, + }, +} + +pub struct Dep { + pub ident: Ident, + pub pat: Option, +} + +fn parse_deps(input: ParseStream) -> Result> { + let mut deps = Vec::new(); + + if input.peek(Token![%]) { + input.parse::()?; + loop { + let ident: Ident = input.parse()?; + let pat = if input.peek(Token![as]) { + input.parse::()?; + // Patterns in syn 2.0 are parsed via `Pat::parse_multi` + // instead of implementing the `Parse` trait directly. + Some(Pat::parse_multi(input)?) + } else { + None + }; + + deps.push(Dep { ident, pat }); + + if !input.peek(Token![,]) { + break; + } + input.parse::()?; + } + } + + Ok(deps) +} + +impl Parse for RsxNode { + fn parse(input: ParseStream) -> Result { + if input.peek(Token![!]) { + input.parse::()?; + let mount: Ident = input.parse()?; + return Ok(RsxNode::Mount(mount)); + } + + let deps = parse_deps(input)?; + + if input.peek(Token![if]) { + input.parse::()?; + let cond: Expr = input.parse()?; + + let content; + braced!(content in input); + + let children = parse_children(&content)?; + return Ok(RsxNode::If { + deps, + cond, + children, + }); + } + + if input.peek(Token![for]) { + input.parse::()?; + // Parse a possibly multi-pattern (e.g. with `|`) for the `for` binding. + let pat: Pat = Pat::parse_multi(input)?; + input.parse::()?; + let expr: Expr = input.parse()?; + + let content; + braced!(content in input); + + let children = parse_children(&content)?; + return Ok(RsxNode::For { + deps, + pat, + expr, + children, + }); + } + + if input.peek(LitStr) { + return Ok(RsxNode::Text(input.parse()?)); + } + + Ok(RsxNode::Component(input.parse()?)) + } +} + +fn parse_children(input: ParseStream) -> Result> { + let mut nodes = Vec::new(); + while !input.is_empty() { + nodes.push(input.parse()?); + } + Ok(nodes) +} diff --git a/src/lib.rs b/src/lib.rs index 690fac9..8b6da82 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ use crate::render::DrawContext; pub mod component; pub mod engine; pub mod frontend; -pub mod macros; pub mod render; pub mod state; @@ -13,9 +12,9 @@ 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; + pub use osui_macros::rsx; } pub type View = Arc; diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index 23c3e7a..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,165 +0,0 @@ -#[macro_export] -macro_rules! rsx { - () => { - $crate::frontend::Rsx::new() - }; - - ($($rsx:tt)+) => {{ - let mut r = $crate::frontend::Rsx::new(); - { - $crate::rsx_scope!(r, $($rsx)+); - } - r - }}; -} - -#[macro_export] -macro_rules! rsx_scope { - ($rsx:expr, for $(%$($dep:ident $(as $dp:pat,)?),+)? ($p:pat in $v:expr) {$($inner:tt)*} $($rest:tt)*) => { - { - $($($crate::rsx_dep!($dep);)+)? - - $rsx.dynamic_scope({ - $($($crate::rsx_dep!($dep $(as $dp)?);)+)? - move |scope| { - scope.children.lock().unwrap().clear(); - for $p in $v { - $crate::rsx_child!(scope, $($inner)*); - } - } - }, vec![$($(Box::new($dep)),+)?]); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, if $(%$($dep:ident $(as $dp:pat,)?),+)? ($st:expr) {$($inner:tt)*} $($rest:tt)*) => { - { - $($($crate::rsx_dep!($dep);)+)? - - $rsx.dynamic_scope({ - $($($crate::rsx_dep!($dep $(as $dp)?);)+)? - move |scope| { - if $st { - if scope.children.lock().unwrap().len() == 0 { - $crate::rsx_child!(scope, $($inner)*); - } - } else { - scope.children.lock().unwrap().clear(); - } - } - }, vec![$($(Box::new($dep)),+)?]); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, $(%$($dep:ident $(as $dp:pat,)?),+)? $text:literal @$d:expr; $($rest:tt)*) => { - { - let scope = $crate::component::Scope::new(); - - $($($crate::rsx_dep!($dep $(as $dp)?);)+)? - - $crate::rsx_child!(scope, $text @$d;); - - $rsx.static_scope(scope); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, $(%$($dep:ident $(as $dp:pat,)?),+)? $text:literal $($rest:tt)*) => { - { - let scope = $crate::component::Scope::new(); - - $($($crate::rsx_dep!($dep $(as $dp)?);)+)? - - $crate::rsx_child!(scope, $text); - - $rsx.static_scope(scope); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { - { - let scope = $crate::component::Scope::new(); - - $crate::rsx_child!(scope, $component ($ctx, $view) $body); - - $rsx.static_scope(scope); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, $component:ident $($rest:tt)*) => { - { - let scope = $crate::component::Scope::new(); - - $crate::rsx_child!(scope, $component); - - $rsx.static_scope(scope); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr, !mount $mount:ident $($rest:tt)*) => { - { - $mount.mount(); - } - - $crate::rsx_scope!($rsx, $($rest)*); - }; - - ($rsx:expr,) => {}; -} - -#[macro_export] -macro_rules! rsx_child { - ($scope:expr, $text:literal @$d:expr; $($rest:tt)*) => { - { - $scope.view(Arc::new(move |ctx| ctx.draw_text($d, &format!($text)))); - } - - $crate::rsx_child!($scope, $($rest)*); - }; - - ($scope:expr, $text:literal $($rest:tt)*) => { - { - $scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &format!($text)))); - } - - $crate::rsx_child!($scope, $($rest)*); - }; - - ($scope:expr, $component:ident ($ctx:ident, $view:ident) $body:block $($rest:tt)*) => { - { - $scope.child($component, Some(Arc::new(|$ctx, $view| $body))); - } - - $crate::rsx_child!($scope, $($rest)*); - }; - - ($scope:expr, $component:ident $($rest:tt)*) => { - { - $scope.child($component, None); - } - $crate::rsx_child!($scope, $($rest)*); - }; - - ($rsx:expr,) => {}; -} - -#[macro_export] -macro_rules! rsx_dep { - ($dep:ident as $dp:pat) => { - let $dp = $dep.clone(); - }; - - ($dep:ident) => { - let $dep = $dep.clone(); - }; -} From 715175c412e858fc19708a8654146b32fc52815d Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 15:27:38 +0100 Subject: [PATCH 3/7] rsx proc macro --- macros/src/emit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macros/src/emit.rs b/macros/src/emit.rs index d909d2a..186f505 100644 --- a/macros/src/emit.rs +++ b/macros/src/emit.rs @@ -1,6 +1,6 @@ use crate::parse::*; use proc_macro2::TokenStream; -use quote::{ToTokens, quote}; +use quote::quote; pub fn emit_rsx(root: RsxRoot) -> TokenStream { let nodes = root.nodes.iter().map(emit_node); From 63abf41094f05f716d7faa9959e16c5a49e50158 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 19:33:12 +0100 Subject: [PATCH 4/7] Implemented commands on scopes --- macros/src/emit.rs | 18 ++++++------------ src/engine/mod.rs | 10 +++++++++- src/frontend.rs | 12 ++++++++---- src/lib.rs | 3 +++ 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/macros/src/emit.rs b/macros/src/emit.rs index 186f505..c4683c9 100644 --- a/macros/src/emit.rs +++ b/macros/src/emit.rs @@ -28,21 +28,15 @@ fn emit_deps(deps: &[Dep]) -> TokenStream { fn emit_node(node: &RsxNode) -> TokenStream { match node { RsxNode::Text(text) => quote! { - { - let scope = osui::component::Scope::new(); - scope.view(Arc::new(move |ctx| { - ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text)) - })); - r.static_scope(scope); - } + r.static_scope(|scope| scope.view(Arc::new(move |ctx| { + ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text)) + }))); }, RsxNode::Component(name) => quote! { - { - let scope = osui::component::Scope::new(); - scope.child(#name, None); - r.static_scope(scope); - } + r.static_scope(|scope| scope.view(Arc::new(move |ctx| { + scope.child(#name, None) + }))); }, RsxNode::Mount(m) => quote! { diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 0385182..a228b4d 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -4,7 +4,7 @@ pub mod console; pub use benchmark::*; pub use console::*; -use std::sync::Arc; +use std::{any::Any, sync::Arc}; use crate::{prelude::Context, render::Area, DrawContext, View}; @@ -30,3 +30,11 @@ pub trait Engine { fn render_view(&self, area: &Area, view: &View) -> DrawContext; fn draw_context(&self, ctx: &DrawContext); } + +pub trait Command { + fn as_any(&self) -> &dyn Any; +} + +pub trait CommandExecutor { + fn execute_command(&self, command: &Arc) -> crate::Result<()>; +} diff --git a/src/frontend.rs b/src/frontend.rs index e90ea99..dbbb7c2 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -7,7 +7,7 @@ use crate::{ }; pub enum RsxScope { - Static(Arc), + Static(Box) + Send + Sync>), Dynamic( Arc) + Send + Sync>, Vec>, @@ -21,8 +21,8 @@ impl Rsx { Self(Vec::new()) } - pub fn static_scope(&mut self, scope: Arc) { - self.0.push(RsxScope::Static(scope)); + pub fn static_scope) + Send + Sync + 'static>(&mut self, scope: F) { + self.0.push(RsxScope::Static(Box::new(scope))); } pub fn dynamic_scope) + Send + Sync + 'static>( @@ -37,7 +37,11 @@ impl Rsx { pub fn view(&self, context: Arc) -> View { for scope in &self.0 { match scope { - RsxScope::Static(scope) => context.scopes.lock().unwrap().push(scope.clone()), + RsxScope::Static(scope_fn) => { + let scope = Scope::new(); + (scope_fn)(&scope); + context.scopes.lock().unwrap().push(scope) + } RsxScope::Dynamic(drawer, dependencies) => { let drawer = drawer.clone(); context.dyn_scope( diff --git a/src/lib.rs b/src/lib.rs index 8b6da82..3590d5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,3 +19,6 @@ pub mod prelude { pub type View = Arc; pub type ViewWrapper = Arc; +pub type Result = std::result::Result; + +pub enum Error {} From 1df6cd4c5e4ad04d5b1edfb59baabe297e737b44 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 23:33:22 +0100 Subject: [PATCH 5/7] Implemented commandExecutor logic --- src/component.rs | 2 ++ src/engine/benchmark.rs | 4 ++++ src/engine/commands.rs | 1 + src/engine/console.rs | 51 ++++++++++++++++++++++++++++++++++++++++- src/engine/mod.rs | 2 ++ src/lib.rs | 10 +++++++- 6 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/engine/commands.rs diff --git a/src/component.rs b/src/component.rs index 40e63c4..5fd828e 100644 --- a/src/component.rs +++ b/src/component.rs @@ -5,6 +5,7 @@ use std::{ }; use crate::{ + engine::CommandExecutor, render::DrawContext, state::{use_effect, HookDependency}, View, ViewWrapper, @@ -22,6 +23,7 @@ pub struct Context { event_handlers: Mutex>>, view: Mutex, pub(crate) scopes: Mutex>>, + // executor: Arc, } impl Context { diff --git a/src/engine/benchmark.rs b/src/engine/benchmark.rs index d3ed0a1..df8c700 100644 --- a/src/engine/benchmark.rs +++ b/src/engine/benchmark.rs @@ -87,4 +87,8 @@ impl Engine for Benchmark { fn render_delay(&self) { self.0.render_delay(); } + + fn executor(&self) -> Arc { + self.0.executor() + } } diff --git a/src/engine/commands.rs b/src/engine/commands.rs new file mode 100644 index 0000000..d93ee1d --- /dev/null +++ b/src/engine/commands.rs @@ -0,0 +1 @@ +pub struct Stop; diff --git a/src/engine/console.rs b/src/engine/console.rs index ddf4d21..bf79937 100644 --- a/src/engine/console.rs +++ b/src/engine/console.rs @@ -5,18 +5,31 @@ use std::{ use crossterm::{cursor::MoveTo, execute, terminal::Clear}; -use crate::{prelude::Context, render::Area, DrawContext, View}; +use crate::{ + engine::{commands, CommandExecutor}, + prelude::Context, + render::Area, + DrawContext, View, +}; use super::Engine; +pub struct ConsoleExecutor { + running: Mutex, +} + pub struct Console { threads: Mutex) + Send + Sync>>>, + executor: Arc, } impl Console { pub fn new() -> Self { Self { threads: Mutex::new(Vec::new()), + executor: Arc::new(ConsoleExecutor { + running: Mutex::new(true), + }), } } @@ -84,4 +97,40 @@ impl Engine for Console { cx } + + fn executor(&self) -> Arc { + self.executor.clone() + } + + fn run) -> View + Send + Sync + 'static>(&self, component: F) { + let cx = self.init(component); + + while self.executor.is_running() { + self.render(&cx); + self.render_delay(); + } + } +} + +impl ConsoleExecutor { + pub fn is_running(self: &Arc) -> bool { + *self.running.lock().unwrap() + } + + pub fn stop(&self) -> crate::Result<()> { + *self.running.lock()? = false; + Ok(()) + } +} + +impl CommandExecutor for ConsoleExecutor { + fn execute_command(&self, command: &Arc) -> crate::Result<()> { + let command = command.as_any(); + + if let Some(commands::Stop) = command.downcast_ref() { + self.stop()?; + } + + Ok(()) + } } diff --git a/src/engine/mod.rs b/src/engine/mod.rs index a228b4d..daf5474 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -1,4 +1,5 @@ pub mod benchmark; +pub mod commands; pub mod console; pub use benchmark::*; @@ -29,6 +30,7 @@ pub trait Engine { fn render_view(&self, area: &Area, view: &View) -> DrawContext; fn draw_context(&self, ctx: &DrawContext); + fn executor(&self) -> Arc; } pub trait Command { diff --git a/src/lib.rs b/src/lib.rs index 3590d5f..b41e0bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,4 +21,12 @@ pub type View = Arc; pub type ViewWrapper = Arc; pub type Result = std::result::Result; -pub enum Error {} +pub enum Error { + PoisonError, +} + +impl From>> for Error { + fn from(_value: std::sync::PoisonError>) -> Self { + Error::PoisonError + } +} From 93ba2f4a7897bbca69a9fe900d4de88a99febc7f Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 23:41:54 +0100 Subject: [PATCH 6/7] Implemented exectution to Context --- src/component.rs | 28 +++++++++++++++++----------- src/engine/console.rs | 2 +- src/engine/mod.rs | 2 +- src/frontend.rs | 4 +++- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/component.rs b/src/component.rs index 5fd828e..e92590f 100644 --- a/src/component.rs +++ b/src/component.rs @@ -16,6 +16,7 @@ pub type EventHandler = Arc, &dyn Any) + Send + Sy pub struct Scope { pub children: Mutex, Option)>>, + executor: Arc, } pub struct Context { @@ -23,16 +24,20 @@ pub struct Context { event_handlers: Mutex>>, view: Mutex, pub(crate) scopes: Mutex>>, - // executor: Arc, + executor: Arc, } impl Context { - pub fn new) -> View + Send + Sync + 'static>(component: F) -> Arc { + pub fn new) -> View + Send + Sync + 'static>( + component: F, + executor: Arc, + ) -> Arc { Arc::new(Self { component: Mutex::new(Arc::new(component)), event_handlers: Mutex::new(HashMap::new()), view: Mutex::new(Arc::new(|_| {})), scopes: Mutex::new(Vec::new()), + executor, }) } @@ -124,9 +129,7 @@ impl Context { } pub fn scope(self: &Arc) -> Arc { - let scope = Arc::new(Scope { - children: Mutex::new(Vec::new()), - }); + let scope = Scope::new(self.executor.clone()); self.scopes.lock().unwrap().push(scope.clone()); scope @@ -137,9 +140,7 @@ impl Context { drawer: F, dependencies: &[&dyn HookDependency], ) -> Arc { - let scope = Arc::new(Scope { - children: Mutex::new(Vec::new()), - }); + let scope = Scope::new(self.executor.clone()); self.scopes.lock().unwrap().push(scope.clone()); drawer(&scope); @@ -170,12 +171,17 @@ impl Context { } } } + + pub fn get_executor(self: &Arc) -> Arc { + self.executor.clone() + } } impl Scope { - pub fn new() -> Arc { + pub fn new(executor: Arc) -> Arc { Arc::new(Self { children: Mutex::new(Vec::new()), + executor, }) } @@ -184,7 +190,7 @@ impl Scope { child: F, view_wrapper: Option, ) { - let ctx = Context::new(child); + let ctx = Context::new(child, self.executor.clone()); ctx.refresh(); @@ -192,7 +198,7 @@ impl Scope { } pub fn view(self: &Arc, view: View) { - let ctx = Context::new(move |_| view.clone()); + let ctx = Context::new(move |_| view.clone(), self.executor.clone()); ctx.refresh(); diff --git a/src/engine/console.rs b/src/engine/console.rs index bf79937..b8553a3 100644 --- a/src/engine/console.rs +++ b/src/engine/console.rs @@ -83,7 +83,7 @@ impl Engine for Console { &self, component: F, ) -> Arc { - let cx = Context::new(component); + let cx = Context::new(component, self.executor.clone()); cx.refresh(); for thread in self.threads.lock().unwrap().iter() { diff --git a/src/engine/mod.rs b/src/engine/mod.rs index daf5474..bd28fb3 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -37,6 +37,6 @@ pub trait Command { fn as_any(&self) -> &dyn Any; } -pub trait CommandExecutor { +pub trait CommandExecutor: Send + Sync { fn execute_command(&self, command: &Arc) -> crate::Result<()>; } diff --git a/src/frontend.rs b/src/frontend.rs index dbbb7c2..bcabbb5 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -35,10 +35,12 @@ impl Rsx { } pub fn view(&self, context: Arc) -> View { + let executor = context.get_executor(); + for scope in &self.0 { match scope { RsxScope::Static(scope_fn) => { - let scope = Scope::new(); + let scope = Scope::new(executor.clone()); (scope_fn)(&scope); context.scopes.lock().unwrap().push(scope) } From 0cf0eb42fc05c055ff932830ac306938fa8ec1fb Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 28 Jan 2026 23:50:08 +0100 Subject: [PATCH 7/7] Context implementation --- src/component.rs | 11 ++++++++++- src/engine/commands.rs | 8 ++++++++ src/lib.rs | 1 + 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/component.rs b/src/component.rs index e92590f..90c910a 100644 --- a/src/component.rs +++ b/src/component.rs @@ -5,7 +5,7 @@ use std::{ }; use crate::{ - engine::CommandExecutor, + engine::{Command, CommandExecutor}, render::DrawContext, state::{use_effect, HookDependency}, View, ViewWrapper, @@ -175,6 +175,15 @@ impl Context { pub fn get_executor(self: &Arc) -> Arc { self.executor.clone() } + + pub fn execute(self: &Arc, command: T) -> crate::Result<()> { + self.executor + .execute_command(&(Arc::new(command) as Arc)) + } + + pub fn stop(self: &Arc) -> crate::Result<()> { + self.execute(crate::engine::commands::Stop) + } } impl Scope { diff --git a/src/engine/commands.rs b/src/engine/commands.rs index d93ee1d..8cda616 100644 --- a/src/engine/commands.rs +++ b/src/engine/commands.rs @@ -1 +1,9 @@ +use crate::engine::Command; + pub struct Stop; + +impl Command for Stop { + fn as_any(&self) -> &dyn std::any::Any { + self + } +} diff --git a/src/lib.rs b/src/lib.rs index b41e0bb..7923bda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub type View = Arc; pub type ViewWrapper = Arc; pub type Result = std::result::Result; +#[derive(Debug, Clone)] pub enum Error { PoisonError, }