Merge pull request #43 from osui-rs/developed-structure
Developed structure
This commit is contained in:
+45
-6
@@ -1,15 +1,54 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use osui::prelude::*;
|
use osui::prelude::*;
|
||||||
|
|
||||||
|
use std::{sync::mpsc, thread};
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let engine = Benchmark::new(Console::new());
|
let engine = Arc::new(Benchmark::new(Console::new()));
|
||||||
engine.run(app);
|
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
let max_threads = 32;
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
for chunk in (0..500).collect::<Vec<_>>().chunks(max_threads) {
|
||||||
|
for &items in chunk {
|
||||||
|
let engine = engine.clone();
|
||||||
|
let tx = tx.clone();
|
||||||
|
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
let res = engine
|
||||||
|
.run(BenchmarkApp { items })
|
||||||
|
.expect("Failed to run engine");
|
||||||
|
|
||||||
|
tx.send((items, res)).expect("Failed to send result");
|
||||||
|
});
|
||||||
|
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
for handle in handles.drain(..) {
|
||||||
|
handle.join().expect("Thread panicked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
let mut results: Vec<(usize, BenchmarkResult)> = rx.iter().collect();
|
||||||
|
results.sort_by_key(|(items, _)| *items);
|
||||||
|
|
||||||
|
for (items, bench) in results {
|
||||||
|
println!("Results for {items} items:\n{bench}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: &Arc<Context>) -> View {
|
#[component]
|
||||||
|
fn BenchmarkApp(cx: &Arc<Context>, items: &usize) -> View {
|
||||||
|
let items = items.clone();
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
|
for _ in (0..items) {
|
||||||
"Hello, world!"
|
"Hello, world!"
|
||||||
}
|
}
|
||||||
.view(cx.clone())
|
}
|
||||||
|
.view(&cx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use osui::prelude::*;
|
use osui::prelude::*;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
let console = Console::new();
|
let engine = Console::new();
|
||||||
console.run(app);
|
engine.run(App {}).expect("Failed to run engine");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: &Arc<Context>) -> View {
|
#[component]
|
||||||
|
fn App(cx: &Arc<Context>) -> View {
|
||||||
rsx! {
|
rsx! {
|
||||||
"Hello, world!"
|
"Hello World"
|
||||||
}
|
}
|
||||||
.view(cx.clone())
|
.view(&cx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
use osui::prelude::*;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let engine = Benchmark::new(Console::new());
|
||||||
|
let benchmark_result = engine.run(App {}).expect("Failed to run engine");
|
||||||
|
println!("Avg: {} μs", benchmark_result.average);
|
||||||
|
println!("Min: {} μs", benchmark_result.min);
|
||||||
|
println!("Max: {} μs", benchmark_result.max);
|
||||||
|
println!("Tot: {} μs", benchmark_result.total);
|
||||||
|
println!("Tot Render: {} μs", benchmark_result.total_render);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App(cx: &Arc<Context>) -> View {
|
||||||
|
rsx! {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
.view(&cx)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use osui::prelude::*;
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let engine = Console::new();
|
||||||
|
engine.run(App {}).expect("Failed to run engine");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn App(cx: &Arc<Context>) -> View {
|
||||||
|
rsx! {
|
||||||
|
MyComponent { "------ example" }
|
||||||
|
}
|
||||||
|
.view(&cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn MyComponent(cx: &Arc<Context>, children: &Rsx) -> View {
|
||||||
|
rsx! {
|
||||||
|
@{children}
|
||||||
|
"Simple"
|
||||||
|
}
|
||||||
|
.view(&cx)
|
||||||
|
}
|
||||||
+97
-15
@@ -3,7 +3,11 @@ use proc_macro2::TokenStream;
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
|
pub fn emit_rsx(root: RsxRoot) -> TokenStream {
|
||||||
let nodes = root.nodes.iter().map(emit_node);
|
emit_rsx_vec(&root.nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emit_rsx_vec(nodes: &Vec<RsxNode>) -> TokenStream {
|
||||||
|
let nodes = nodes.iter().map(emit_node_scope);
|
||||||
|
|
||||||
quote! {{
|
quote! {{
|
||||||
let mut r = osui::frontend::Rsx::new();
|
let mut r = osui::frontend::Rsx::new();
|
||||||
@@ -25,19 +29,35 @@ fn emit_deps(deps: &[Dep]) -> TokenStream {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emit_node(node: &RsxNode) -> TokenStream {
|
fn emit_deps_vec(deps: &[Dep]) -> TokenStream {
|
||||||
match node {
|
let deps = deps.iter().map(|d| {
|
||||||
RsxNode::Text(text) => quote! {
|
let ident = &d.ident;
|
||||||
r.static_scope(|scope| scope.view(Arc::new(move |ctx| {
|
|
||||||
ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text))
|
|
||||||
})));
|
|
||||||
},
|
|
||||||
|
|
||||||
RsxNode::Component(name) => quote! {
|
quote! {
|
||||||
r.static_scope(|scope| scope.view(Arc::new(move |ctx| {
|
std::sync::Arc::new(#ident) as std::sync::Arc<dyn HookDependency>
|
||||||
scope.child(#name, None)
|
}
|
||||||
})));
|
});
|
||||||
},
|
|
||||||
|
quote! {
|
||||||
|
vec![ #(#deps),* ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_node_scope(node: &RsxNode) -> TokenStream {
|
||||||
|
match node {
|
||||||
|
RsxNode::Text(_) => {
|
||||||
|
let emit = emit_node(node);
|
||||||
|
quote! {
|
||||||
|
r.static_scope(move |scope| {#emit});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RsxNode::Component { .. } => {
|
||||||
|
let emit = emit_node(node);
|
||||||
|
quote! {
|
||||||
|
r.static_scope(move |scope| {#emit});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RsxNode::Mount(m) => quote! {
|
RsxNode::Mount(m) => quote! {
|
||||||
#m.mount();
|
#m.mount();
|
||||||
@@ -49,6 +69,7 @@ fn emit_node(node: &RsxNode) -> TokenStream {
|
|||||||
children,
|
children,
|
||||||
} => {
|
} => {
|
||||||
let deps_emit = emit_deps(deps);
|
let deps_emit = emit_deps(deps);
|
||||||
|
let deps_vec_emit = emit_deps_vec(deps);
|
||||||
let kids = children.iter().map(emit_node);
|
let kids = children.iter().map(emit_node);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
@@ -62,7 +83,7 @@ fn emit_node(node: &RsxNode) -> TokenStream {
|
|||||||
} else {
|
} else {
|
||||||
scope.children.lock().unwrap().clear();
|
scope.children.lock().unwrap().clear();
|
||||||
}
|
}
|
||||||
});
|
}, #deps_vec_emit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,19 +95,80 @@ fn emit_node(node: &RsxNode) -> TokenStream {
|
|||||||
children,
|
children,
|
||||||
} => {
|
} => {
|
||||||
let deps_emit = emit_deps(deps);
|
let deps_emit = emit_deps(deps);
|
||||||
|
let deps_vec_emit = emit_deps_vec(deps);
|
||||||
let kids = children.iter().map(emit_node);
|
let kids = children.iter().map(emit_node);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
#deps_emit
|
#deps_emit
|
||||||
|
#[allow(unused_parens)]
|
||||||
r.dynamic_scope(move |scope| {
|
r.dynamic_scope(move |scope| {
|
||||||
scope.children.lock().unwrap().clear();
|
scope.children.lock().unwrap().clear();
|
||||||
for #pat in #expr {
|
for #pat in #expr {
|
||||||
#(#kids)*
|
#(#kids)*
|
||||||
}
|
}
|
||||||
});
|
}, #deps_vec_emit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RsxNode::Expr(expr) => quote! {
|
||||||
|
r.child(#expr);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_node(node: &RsxNode) -> TokenStream {
|
||||||
|
match node {
|
||||||
|
RsxNode::Text(text) => quote! {
|
||||||
|
scope.view(Arc::new(move |ctx| {
|
||||||
|
ctx.draw_text(Point { x: 0, y: 0 }, &format!(#text))
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
} => {
|
||||||
|
let prop_inits = props.iter().map(|p| {
|
||||||
|
let name = &p.name;
|
||||||
|
let value = &p.value;
|
||||||
|
quote! { #name: #value }
|
||||||
|
});
|
||||||
|
|
||||||
|
let emit_children = emit_rsx_vec(children);
|
||||||
|
|
||||||
|
let component_expr = if children.len() > 0 {
|
||||||
|
quote! {
|
||||||
|
#path {
|
||||||
|
#(#prop_inits,)*
|
||||||
|
children: #emit_children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#path {
|
||||||
|
#(#prop_inits,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
scope.child(#component_expr, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RsxNode::Mount(m) => quote! {
|
||||||
|
#m.mount();
|
||||||
|
},
|
||||||
|
|
||||||
|
RsxNode::If { .. } => panic!("Invalid if statement"),
|
||||||
|
|
||||||
|
RsxNode::For { .. } => panic!("Invalid for loop"),
|
||||||
|
|
||||||
|
RsxNode::Expr(expr) => quote! {
|
||||||
|
r.child(#expr);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-1
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
+95
-8
@@ -1,7 +1,9 @@
|
|||||||
use syn::braced;
|
use syn::braced;
|
||||||
|
use syn::parse::discouraged::Speculative;
|
||||||
use syn::{
|
use syn::{
|
||||||
Expr, Ident, LitStr, Pat, Result, Token,
|
Expr, Ident, LitStr, Pat, Path, Result, Token,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
|
token::Brace,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RsxRoot {
|
pub struct RsxRoot {
|
||||||
@@ -18,9 +20,20 @@ impl Parse for RsxRoot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A single prop: `ident: expr`.
|
||||||
|
pub struct RsxProp {
|
||||||
|
pub name: Ident,
|
||||||
|
pub value: Expr,
|
||||||
|
}
|
||||||
|
|
||||||
pub enum RsxNode {
|
pub enum RsxNode {
|
||||||
Text(LitStr),
|
Text(LitStr),
|
||||||
Component(Ident),
|
Expr(Expr),
|
||||||
|
Component {
|
||||||
|
path: Path,
|
||||||
|
props: Vec<RsxProp>,
|
||||||
|
children: Vec<RsxNode>,
|
||||||
|
},
|
||||||
Mount(Ident),
|
Mount(Ident),
|
||||||
If {
|
If {
|
||||||
deps: Vec<Dep>,
|
deps: Vec<Dep>,
|
||||||
@@ -70,6 +83,18 @@ fn parse_deps(input: ParseStream) -> Result<Vec<Dep>> {
|
|||||||
|
|
||||||
impl Parse for RsxNode {
|
impl Parse for RsxNode {
|
||||||
fn parse(input: ParseStream) -> Result<Self> {
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
// @{ $expr }
|
||||||
|
if input.peek(Token![@]) {
|
||||||
|
input.parse::<Token![@]>()?;
|
||||||
|
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
|
||||||
|
let expr: Expr = content.parse()?;
|
||||||
|
return Ok(RsxNode::Expr(expr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// !$ident
|
||||||
if input.peek(Token![!]) {
|
if input.peek(Token![!]) {
|
||||||
input.parse::<Token![!]>()?;
|
input.parse::<Token![!]>()?;
|
||||||
let mount: Ident = input.parse()?;
|
let mount: Ident = input.parse()?;
|
||||||
@@ -78,13 +103,12 @@ impl Parse for RsxNode {
|
|||||||
|
|
||||||
let deps = parse_deps(input)?;
|
let deps = parse_deps(input)?;
|
||||||
|
|
||||||
|
// %$dep if $expr { $rsx }
|
||||||
if input.peek(Token![if]) {
|
if input.peek(Token![if]) {
|
||||||
input.parse::<Token![if]>()?;
|
input.parse::<Token![if]>()?;
|
||||||
let cond: Expr = input.parse()?;
|
let cond: Expr = input.parse()?;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
braced!(content in input);
|
braced!(content in input);
|
||||||
|
|
||||||
let children = parse_children(&content)?;
|
let children = parse_children(&content)?;
|
||||||
return Ok(RsxNode::If {
|
return Ok(RsxNode::If {
|
||||||
deps,
|
deps,
|
||||||
@@ -93,16 +117,14 @@ impl Parse for RsxNode {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// %$dep for $pat in $expr { $rsx }
|
||||||
if input.peek(Token![for]) {
|
if input.peek(Token![for]) {
|
||||||
input.parse::<Token![for]>()?;
|
input.parse::<Token![for]>()?;
|
||||||
// Parse a possibly multi-pattern (e.g. with `|`) for the `for` binding.
|
|
||||||
let pat: Pat = Pat::parse_multi(input)?;
|
let pat: Pat = Pat::parse_multi(input)?;
|
||||||
input.parse::<Token![in]>()?;
|
input.parse::<Token![in]>()?;
|
||||||
let expr: Expr = input.parse()?;
|
let expr: Expr = input.parse()?;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
braced!(content in input);
|
braced!(content in input);
|
||||||
|
|
||||||
let children = parse_children(&content)?;
|
let children = parse_children(&content)?;
|
||||||
return Ok(RsxNode::For {
|
return Ok(RsxNode::For {
|
||||||
deps,
|
deps,
|
||||||
@@ -112,14 +134,79 @@ impl Parse for RsxNode {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "%$dep for $pat in $expr { $rsx }"
|
||||||
if input.peek(LitStr) {
|
if input.peek(LitStr) {
|
||||||
return Ok(RsxNode::Text(input.parse()?));
|
return Ok(RsxNode::Text(input.parse()?));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(RsxNode::Component(input.parse()?))
|
parse_component_invocation(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses Path or Path { ... } into RsxNode::Component.
|
||||||
|
fn parse_component_invocation(input: ParseStream) -> Result<RsxNode> {
|
||||||
|
let path: Path = input.parse()?;
|
||||||
|
|
||||||
|
if !input.peek(Brace) {
|
||||||
|
return Ok(RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props: Vec::new(),
|
||||||
|
children: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let content;
|
||||||
|
braced!(content in input);
|
||||||
|
|
||||||
|
let (props, children) = parse_props_and_children(&content)?;
|
||||||
|
|
||||||
|
Ok(RsxNode::Component {
|
||||||
|
path,
|
||||||
|
props,
|
||||||
|
children,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_props_and_children(input: ParseStream) -> Result<(Vec<RsxProp>, Vec<RsxNode>)> {
|
||||||
|
Ok((parse_props(input)?, RsxRoot::parse(input)?.nodes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_props(input: ParseStream) -> Result<Vec<RsxProp>> {
|
||||||
|
let mut props = Vec::new();
|
||||||
|
|
||||||
|
while !input.is_empty() {
|
||||||
|
let fork = input.fork();
|
||||||
|
|
||||||
|
let path: Path = match fork.parse() {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
if fork.peek(Token![:]) {
|
||||||
|
let name = path
|
||||||
|
.get_ident()
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| fork.error("prop name must be a single identifier"))?;
|
||||||
|
|
||||||
|
fork.parse::<Token![:]>()?;
|
||||||
|
let value: Expr = fork.parse()?;
|
||||||
|
|
||||||
|
input.advance_to(&fork);
|
||||||
|
|
||||||
|
props.push(RsxProp { name, value });
|
||||||
|
|
||||||
|
if input.peek(Token![,]) {
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// ❌ do not commit → nothing consumed
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(props)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_children(input: ParseStream) -> Result<Vec<RsxNode>> {
|
fn parse_children(input: ParseStream) -> Result<Vec<RsxNode>> {
|
||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
|
|||||||
@@ -5,19 +5,14 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
component::EventHandler,
|
||||||
engine::{Command, CommandExecutor},
|
engine::{Command, CommandExecutor},
|
||||||
render::DrawContext,
|
render::DrawContext,
|
||||||
state::{use_effect, HookDependency},
|
state::{use_effect, HookDependency},
|
||||||
View, ViewWrapper,
|
View,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Component = Arc<dyn Fn(&Arc<Context>) -> View + Send + Sync>;
|
use super::{scope::Scope, Component, ComponentImpl};
|
||||||
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
|
|
||||||
|
|
||||||
pub struct Scope {
|
|
||||||
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
|
|
||||||
executor: Arc<dyn CommandExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
component: Mutex<Component>,
|
component: Mutex<Component>,
|
||||||
@@ -28,7 +23,7 @@ pub struct Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new<F: Fn(&Arc<Self>) -> View + Send + Sync + 'static>(
|
pub fn new<F: ComponentImpl + 'static>(
|
||||||
component: F,
|
component: F,
|
||||||
executor: Arc<dyn CommandExecutor>,
|
executor: Arc<dyn CommandExecutor>,
|
||||||
) -> Arc<Self> {
|
) -> Arc<Self> {
|
||||||
@@ -48,7 +43,7 @@ impl Context {
|
|||||||
move || {
|
move || {
|
||||||
s.event_handlers.lock().unwrap().clear();
|
s.event_handlers.lock().unwrap().clear();
|
||||||
let c = s.component.lock().unwrap().clone();
|
let c = s.component.lock().unwrap().clone();
|
||||||
*s.view.lock().unwrap() = (c)(&s);
|
*s.view.lock().unwrap() = c.call(&s);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -62,7 +57,7 @@ impl Context {
|
|||||||
move || {
|
move || {
|
||||||
s.event_handlers.lock().unwrap().clear();
|
s.event_handlers.lock().unwrap().clear();
|
||||||
let c = s.component.lock().unwrap().clone();
|
let c = s.component.lock().unwrap().clone();
|
||||||
*s.view.lock().unwrap() = (c)(&s);
|
*s.view.lock().unwrap() = c.call(&s);
|
||||||
*done.lock().unwrap() = true;
|
*done.lock().unwrap() = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -185,32 +180,3 @@ impl Context {
|
|||||||
self.execute(crate::engine::commands::Stop)
|
self.execute(crate::engine::commands::Stop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
|
|
||||||
Arc::new(Self {
|
|
||||||
children: Mutex::new(Vec::new()),
|
|
||||||
executor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn child<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
|
|
||||||
self: &Arc<Self>,
|
|
||||||
child: F,
|
|
||||||
view_wrapper: Option<ViewWrapper>,
|
|
||||||
) {
|
|
||||||
let ctx = Context::new(child, self.executor.clone());
|
|
||||||
|
|
||||||
ctx.refresh();
|
|
||||||
|
|
||||||
self.children.lock().unwrap().push((ctx, view_wrapper));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn view(self: &Arc<Self>, view: View) {
|
|
||||||
let ctx = Context::new(move |_| view.clone(), self.executor.clone());
|
|
||||||
|
|
||||||
ctx.refresh();
|
|
||||||
|
|
||||||
self.children.lock().unwrap().push((ctx, None));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
pub mod context;
|
||||||
|
pub mod scope;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::View;
|
||||||
|
|
||||||
|
use context::Context;
|
||||||
|
|
||||||
|
pub type Component = Arc<dyn ComponentImpl>;
|
||||||
|
pub type EventHandler = Arc<Mutex<dyn FnMut(&Arc<Context>, &dyn Any) + Send + Sync>>;
|
||||||
|
|
||||||
|
pub trait ComponentImpl: Send + Sync {
|
||||||
|
fn call(&self, cx: &Arc<Context>) -> View;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentImpl for View {
|
||||||
|
fn call(&self, _: &Arc<Context>) -> View {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> ComponentImpl for F
|
||||||
|
where
|
||||||
|
F: Fn(&Arc<Context>) -> View + Send + Sync,
|
||||||
|
{
|
||||||
|
fn call(&self, ctx: &Arc<Context>) -> View {
|
||||||
|
self(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use crate::{engine::CommandExecutor, View, ViewWrapper};
|
||||||
|
|
||||||
|
use super::{context::Context, ComponentImpl};
|
||||||
|
|
||||||
|
pub struct Scope {
|
||||||
|
pub children: Mutex<Vec<(Arc<Context>, Option<ViewWrapper>)>>,
|
||||||
|
executor: Arc<dyn CommandExecutor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scope {
|
||||||
|
pub fn new(executor: Arc<dyn CommandExecutor>) -> Arc<Self> {
|
||||||
|
Arc::new(Self {
|
||||||
|
children: Mutex::new(Vec::new()),
|
||||||
|
executor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child<F: ComponentImpl + 'static>(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
child: F,
|
||||||
|
view_wrapper: Option<ViewWrapper>,
|
||||||
|
) {
|
||||||
|
let ctx = Context::new(child, self.executor.clone());
|
||||||
|
|
||||||
|
ctx.refresh();
|
||||||
|
|
||||||
|
self.children.lock().unwrap().push((ctx, view_wrapper));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(self: &Arc<Self>, view: View) {
|
||||||
|
let ctx = Context::new(view, self.executor.clone());
|
||||||
|
|
||||||
|
ctx.refresh();
|
||||||
|
|
||||||
|
self.children.lock().unwrap().push((ctx, None));
|
||||||
|
}
|
||||||
|
}
|
||||||
+40
-37
@@ -2,10 +2,20 @@ use std::{io::stdout, sync::Arc, time::Instant};
|
|||||||
|
|
||||||
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
||||||
|
|
||||||
use crate::{prelude::Context, render::Area, DrawContext, View};
|
use crate::component::{context::Context, ComponentImpl};
|
||||||
|
use crate::{render::Area, DrawContext, View};
|
||||||
|
|
||||||
use super::Engine;
|
use super::Engine;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BenchmarkResult {
|
||||||
|
pub average: u128,
|
||||||
|
pub min: u128,
|
||||||
|
pub max: u128,
|
||||||
|
pub total_render: u128,
|
||||||
|
pub total: u128,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Benchmark<T: Engine>(T);
|
pub struct Benchmark<T: Engine>(T);
|
||||||
|
|
||||||
impl<T: Engine> Benchmark<T> {
|
impl<T: Engine> Benchmark<T> {
|
||||||
@@ -14,19 +24,18 @@ impl<T: Engine> Benchmark<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Engine> Engine for Benchmark<T> {
|
impl<T: Engine> Engine<BenchmarkResult> for Benchmark<T> {
|
||||||
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
|
fn run<F: ComponentImpl + 'static>(&self, component: F) -> crate::Result<BenchmarkResult> {
|
||||||
let mut times: Vec<usize> = Vec::new();
|
let mut times: Vec<u128> = Vec::new();
|
||||||
let cx = self.init(component);
|
let cx = self.init(component);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
for _ in 0..100 {
|
for _ in 0..40 {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
self.render(&cx);
|
self.render(&cx);
|
||||||
let end = Instant::now();
|
let end = Instant::now();
|
||||||
times.push(end.duration_since(start).as_millis() as usize);
|
times.push(end.duration_since(start).as_micros());
|
||||||
self.0.render_delay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let end = Instant::now();
|
let end = Instant::now();
|
||||||
@@ -35,40 +44,20 @@ impl<T: Engine> Engine for Benchmark<T> {
|
|||||||
execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap();
|
execute!(stdout(), Clear(crossterm::terminal::ClearType::All)).unwrap();
|
||||||
execute!(stdout(), MoveTo(0, 0)).unwrap();
|
execute!(stdout(), MoveTo(0, 0)).unwrap();
|
||||||
|
|
||||||
println!(
|
Ok(BenchmarkResult {
|
||||||
"Average time: {}ms",
|
min: *times.iter().min().unwrap_or(&0),
|
||||||
if times.len() > 0 {
|
max: *times.iter().max().unwrap_or(&0),
|
||||||
times.iter().sum::<usize>() / times.len()
|
total: end.duration_since(start).as_micros(),
|
||||||
|
total_render: times.iter().sum::<u128>(),
|
||||||
|
average: if times.len() > 0 {
|
||||||
|
times.iter().sum::<u128>() / (times.len() as u128)
|
||||||
} else {
|
} else {
|
||||||
0
|
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>(
|
fn init<F: ComponentImpl + 'static>(&self, component: F) -> Arc<Context> {
|
||||||
&self,
|
|
||||||
component: F,
|
|
||||||
) -> Arc<Context> {
|
|
||||||
self.0.init(component)
|
self.0.init(component)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,3 +81,17 @@ impl<T: Engine> Engine for Benchmark<T> {
|
|||||||
self.0.executor()
|
self.0.executor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for BenchmarkResult {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"- Average: {} µs\n\
|
||||||
|
- Min: {} µs\n\
|
||||||
|
- Max: {} µs\n\
|
||||||
|
- Total Render: {} µs\n\
|
||||||
|
- Total: {} µs",
|
||||||
|
self.average, self.min, self.max, self.total_render, self.total
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ use std::{
|
|||||||
|
|
||||||
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
use crossterm::{cursor::MoveTo, execute, terminal::Clear};
|
||||||
|
|
||||||
|
use crate::component::{context::Context, ComponentImpl};
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{commands, CommandExecutor},
|
engine::{commands, CommandExecutor},
|
||||||
prelude::Context,
|
|
||||||
render::Area,
|
render::Area,
|
||||||
DrawContext, View,
|
DrawContext, View,
|
||||||
};
|
};
|
||||||
@@ -79,10 +79,9 @@ impl Engine for Console {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(
|
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context> {
|
||||||
&self,
|
crossterm::execute!(std::io::stdout(), crossterm::cursor::Hide).unwrap();
|
||||||
component: F,
|
|
||||||
) -> Arc<Context> {
|
|
||||||
let cx = Context::new(component, self.executor.clone());
|
let cx = Context::new(component, self.executor.clone());
|
||||||
cx.refresh();
|
cx.refresh();
|
||||||
|
|
||||||
@@ -102,13 +101,15 @@ impl Engine for Console {
|
|||||||
self.executor.clone()
|
self.executor.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
|
fn run<F: ComponentImpl + 'static>(&self, component: F) -> crate::Result<()> {
|
||||||
let cx = self.init(component);
|
let cx = self.init(component);
|
||||||
|
|
||||||
while self.executor.is_running() {
|
while self.executor.is_running() {
|
||||||
self.render(&cx);
|
self.render(&cx);
|
||||||
self.render_delay();
|
self.render_delay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-16
@@ -7,25 +7,15 @@ pub use console::*;
|
|||||||
|
|
||||||
use std::{any::Any, sync::Arc};
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
use crate::{prelude::Context, render::Area, DrawContext, View};
|
use crate::component::{context::Context, ComponentImpl};
|
||||||
|
use crate::{render::Area, DrawContext, View};
|
||||||
|
|
||||||
pub trait Engine {
|
pub trait Engine<Output = ()> {
|
||||||
fn run<F: Fn(&Arc<Context>) -> View + Send + Sync + 'static>(&self, component: F) {
|
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<Output>;
|
||||||
let cx = self.init(component);
|
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context>;
|
||||||
|
|
||||||
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(&self, cx: &Arc<Context>);
|
||||||
fn render_delay(&self) {
|
fn render_delay(&self) {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(16))
|
crate::sleep(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
|
fn render_view(&self, area: &Area, view: &View) -> DrawContext;
|
||||||
|
|||||||
+44
-14
@@ -1,19 +1,23 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::component::{context::Context, scope::Scope};
|
||||||
component::{Context, Scope},
|
use crate::{render::Point, state::HookDependency, View};
|
||||||
state::HookDependency,
|
|
||||||
View,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum RsxScope {
|
pub trait ToRsx {
|
||||||
Static(Box<dyn Fn(&Arc<Scope>) + Send + Sync>),
|
fn to_rsx(&self) -> Rsx;
|
||||||
Dynamic(
|
|
||||||
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
|
|
||||||
Vec<Box<dyn HookDependency>>,
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum RsxScope {
|
||||||
|
Static(Arc<dyn Fn(&Arc<Scope>) + Send + Sync>),
|
||||||
|
Dynamic(
|
||||||
|
Arc<dyn Fn(&Arc<Scope>) + Send + Sync>,
|
||||||
|
Vec<Arc<dyn HookDependency>>,
|
||||||
|
),
|
||||||
|
Child(Rsx),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Rsx(Vec<RsxScope>);
|
pub struct Rsx(Vec<RsxScope>);
|
||||||
|
|
||||||
impl Rsx {
|
impl Rsx {
|
||||||
@@ -22,19 +26,23 @@ impl Rsx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F) {
|
pub fn static_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(&mut self, scope: F) {
|
||||||
self.0.push(RsxScope::Static(Box::new(scope)));
|
self.0.push(RsxScope::Static(Arc::new(scope)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
pub fn dynamic_scope<F: Fn(&Arc<Scope>) + Send + Sync + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
drawer: F,
|
drawer: F,
|
||||||
dependencies: Vec<Box<dyn HookDependency>>,
|
dependencies: Vec<Arc<dyn HookDependency>>,
|
||||||
) {
|
) {
|
||||||
self.0
|
self.0
|
||||||
.push(RsxScope::Dynamic(Arc::new(drawer), dependencies));
|
.push(RsxScope::Dynamic(Arc::new(drawer), dependencies));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self, context: Arc<Context>) -> View {
|
pub fn child<R: ToRsx>(&mut self, child: R) {
|
||||||
|
self.0.push(RsxScope::Child(child.to_rsx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_children(&self, context: &Arc<Context>) {
|
||||||
let executor = context.get_executor();
|
let executor = context.get_executor();
|
||||||
|
|
||||||
for scope in &self.0 {
|
for scope in &self.0 {
|
||||||
@@ -51,10 +59,16 @@ impl Rsx {
|
|||||||
&dependencies.iter().map(|d| d.as_ref()).collect::<Vec<_>>(),
|
&dependencies.iter().map(|d| d.as_ref()).collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
RsxScope::Child(child) => child.generate_children(context),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view(&self, context: &Arc<Context>) -> View {
|
||||||
let context = context.clone();
|
let context = context.clone();
|
||||||
|
|
||||||
|
self.generate_children(&context);
|
||||||
|
|
||||||
Arc::new({
|
Arc::new({
|
||||||
move |ctx| {
|
move |ctx| {
|
||||||
context.draw_children(ctx);
|
context.draw_children(ctx);
|
||||||
@@ -62,3 +76,19 @@ impl Rsx {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToRsx for &Rsx {
|
||||||
|
fn to_rsx(&self) -> Rsx {
|
||||||
|
Rsx(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: std::fmt::Display> ToRsx for T {
|
||||||
|
fn to_rsx(&self) -> Rsx {
|
||||||
|
let s = self.to_string();
|
||||||
|
Rsx(vec![RsxScope::Static(Arc::new(move |scope| {
|
||||||
|
let s = s.clone();
|
||||||
|
scope.view(Arc::new(move |ctx| ctx.draw_text(Point { x: 0, y: 0 }, &s)))
|
||||||
|
}))])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+10
-3
@@ -9,12 +9,15 @@ pub mod render;
|
|||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::component::*;
|
pub use crate::component::{context::*, scope::*, *};
|
||||||
pub use crate::engine::*;
|
pub use crate::engine::*;
|
||||||
|
pub use crate::frontend::*;
|
||||||
pub use crate::render::*;
|
pub use crate::render::*;
|
||||||
pub use crate::state::*;
|
pub use crate::state::*;
|
||||||
pub use crate::View;
|
pub use crate::{sleep, Error, Result, View, ViewWrapper};
|
||||||
pub use osui_macros::rsx;
|
pub use crossterm;
|
||||||
|
pub use osui_macros::{component, rsx};
|
||||||
|
pub use std::sync::{Arc, Mutex};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>;
|
pub type View = Arc<dyn Fn(&mut DrawContext) + Send + Sync>;
|
||||||
@@ -31,3 +34,7 @@ impl From<std::sync::PoisonError<std::sync::MutexGuard<'_, bool>>> for Error {
|
|||||||
Error::PoisonError
|
Error::PoisonError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sleep(delay_ms: u64) {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(delay_ms));
|
||||||
|
}
|
||||||
|
|||||||
+3
-3
@@ -5,7 +5,7 @@ use std::{
|
|||||||
sync::{Arc, Mutex, MutexGuard},
|
sync::{Arc, Mutex, MutexGuard},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::prelude::Context;
|
use crate::component::context::Context;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
|
pub struct HookEffect(Arc<Mutex<dyn FnMut() + Send + Sync>>);
|
||||||
@@ -115,11 +115,11 @@ impl<T> DerefMut for Inner<'_, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait HookDependency {
|
pub trait HookDependency: Send + Sync {
|
||||||
fn on_update(&self, hook: HookEffect);
|
fn on_update(&self, hook: HookEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> HookDependency for State<T> {
|
impl<T: Send + Sync> HookDependency for State<T> {
|
||||||
fn on_update(&self, hook: HookEffect) {
|
fn on_update(&self, hook: HookEffect) {
|
||||||
self.dependents.lock().unwrap().push(hook);
|
self.dependents.lock().unwrap().push(hook);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user