component generator

This commit is contained in:
2026-01-30 19:29:52 +01:00
parent 1f64b4f6e2
commit e85257a10e
6 changed files with 125 additions and 32 deletions
+4 -6
View File
@@ -17,7 +17,7 @@ pub fn main() {
let handle = thread::spawn(move || { let handle = thread::spawn(move || {
let res = engine let res = engine
.run(BenchmarkApp(items)) .run(BenchmarkApp { items })
.expect("Failed to run engine"); .expect("Failed to run engine");
tx.send((items, res)).expect("Failed to send result"); tx.send((items, res)).expect("Failed to send result");
@@ -41,16 +41,14 @@ pub fn main() {
} }
} }
struct BenchmarkApp(pub usize); #[component]
fn BenchmarkApp(cx: &Arc<Context>, items: &usize) -> View {
let items = items.clone();
impl ComponentImpl for BenchmarkApp {
fn call(&self, cx: &Arc<Context>) -> View {
let items = self.0.clone();
rsx! { rsx! {
for _ in (0..items) { for _ in (0..items) {
"Hello, world!" "Hello, world!"
} }
} }
.view(cx.clone()) .view(cx.clone())
}
} }
+4 -7
View File
@@ -4,16 +4,13 @@ use osui::prelude::*;
pub fn main() { pub fn main() {
let engine = Console::new(); let engine = Console::new();
engine.run(App).expect("Failed to run engine"); engine.run(App {}).expect("Failed to run engine");
} }
struct App; #[component]
fn App(cx: &Arc<Context>) -> View {
impl ComponentImpl for App {
fn call(&self, cx: &Arc<Context>) -> View {
rsx! { rsx! {
"Hello, world!" "Hello World"
} }
.view(cx.clone()) .view(cx.clone())
}
} }
+3 -6
View File
@@ -4,7 +4,7 @@ use osui::prelude::*;
pub fn main() { pub fn main() {
let engine = Benchmark::new(Console::new()); let engine = Benchmark::new(Console::new());
let benchmark_result = engine.run(App).expect("Failed to run engine"); let benchmark_result = engine.run(App {}).expect("Failed to run engine");
println!("Avg: {} μs", benchmark_result.average); println!("Avg: {} μs", benchmark_result.average);
println!("Min: {} μs", benchmark_result.min); println!("Min: {} μs", benchmark_result.min);
println!("Max: {} μs", benchmark_result.max); println!("Max: {} μs", benchmark_result.max);
@@ -12,13 +12,10 @@ pub fn main() {
println!("Tot Render: {} μs", benchmark_result.total_render); println!("Tot Render: {} μs", benchmark_result.total_render);
} }
struct App; #[component]
fn App(cx: &Arc<Context>) -> View {
impl ComponentImpl for App {
fn call(&self, cx: &Arc<Context>) -> View {
rsx! { rsx! {
"Hello, world!" "Hello, world!"
} }
.view(cx.clone()) .view(cx.clone())
}
} }
+2 -2
View File
@@ -51,14 +51,14 @@ fn emit_node_scope(node: &RsxNode) -> TokenStream {
RsxNode::Text(_) => { RsxNode::Text(_) => {
let emit = emit_node(node); let emit = emit_node(node);
quote! { quote! {
r.static_scope(|scope| {#emit}); r.static_scope(move |scope| {#emit});
} }
} }
RsxNode::Component(_) => { RsxNode::Component(_) => {
let emit = emit_node(node); let emit = emit_node(node);
quote! { quote! {
r.static_scope(|scope| {#emit}); r.static_scope(move |scope| {#emit});
} }
} }
+102 -1
View File
@@ -1,10 +1,111 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote;
use syn::{FnArg, ItemFn, Pat, ReturnType, Type, parse_macro_input};
mod emit; mod emit;
mod parse; mod parse;
#[proc_macro] #[proc_macro]
pub fn rsx(input: TokenStream) -> TokenStream { pub fn rsx(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as parse::RsxRoot); let ast = parse_macro_input!(input as parse::RsxRoot);
emit::emit_rsx(ast).into() emit::emit_rsx(ast).into()
} }
#[proc_macro_attribute]
pub fn component(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as ItemFn);
let name = &input.sig.ident;
let vis = &input.vis;
let body = &input.block;
let return_ty = match &input.sig.output {
ReturnType::Type(_, ty) => ty,
_ => {
return syn::Error::new_spanned(&input.sig, "component must return View")
.to_compile_error()
.into();
}
};
let mut inputs = input.sig.inputs.iter();
// ---- First param must be cx ----
let cx = match inputs.next() {
Some(FnArg::Typed(pat)) => pat,
_ => {
return syn::Error::new_spanned(&input.sig, "first argument must be cx: &Arc<Context>")
.to_compile_error()
.into();
}
};
let cx_ident = match &*cx.pat {
Pat::Ident(id) => &id.ident,
_ => unreachable!(),
};
let cx_ty = &cx.ty;
// ---- Remaining params are props ----
let mut struct_fields = Vec::new();
let mut render_params = Vec::new();
let mut call_args = Vec::new();
for arg in inputs {
let FnArg::Typed(pat) = arg else { continue };
let ident = match &*pat.pat {
Pat::Ident(id) => &id.ident,
_ => {
return syn::Error::new_spanned(pat, "unsupported prop pattern")
.to_compile_error()
.into();
}
};
// Strip leading &
let owned_ty = match &*pat.ty {
Type::Reference(r) => &r.elem,
ty => ty,
};
struct_fields.push(quote! {
pub #ident: #owned_ty
});
render_params.push(quote! {
#ident: &#owned_ty
});
call_args.push(quote! {
&self.#ident
});
}
let expanded = quote! {
#vis struct #name {
#(#struct_fields,)*
}
impl #name {
pub fn component(
#cx_ident: #cx_ty,
#(#render_params,)*
) -> #return_ty {
#body
}
}
impl ComponentImpl for #name {
fn call(&self, cx: &std::sync::Arc<Context>) -> #return_ty {
Self::component(
cx,
#(#call_args,)*
)
}
}
};
expanded.into()
}
+1 -1
View File
@@ -14,7 +14,7 @@ pub mod prelude {
pub use crate::render::*; pub use crate::render::*;
pub use crate::state::*; pub use crate::state::*;
pub use crate::View; pub use crate::View;
pub use osui_macros::rsx; pub use osui_macros::{component, rsx};
} }
pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>; pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>;