Improved docs
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
---
|
||||
sidebar_position: 0
|
||||
title: Performance Benchmarking
|
||||
---
|
||||
|
||||
# Performance Benchmarking
|
||||
|
||||
Optimizing rendering performance is crucial for smooth and responsive TUI applications, especially those with complex layouts or frequent updates. OSUI provides a built-in `Benchmark` engine wrapper that allows you to easily measure the rendering speed of your components.
|
||||
|
||||
## The `Benchmark` Engine
|
||||
|
||||
The `osui::engine::benchmark` module offers the `Benchmark<T: Engine>` struct, which wraps an existing engine (like `Console`) and records detailed timing information for its rendering cycles.
|
||||
|
||||
### How it works:
|
||||
|
||||
1. You instantiate a `Benchmark` by passing it another `Engine` (e.g., `Console::new()`).
|
||||
2. When you call `benchmark_engine.run(YourApp {})`, the `Benchmark` engine takes over.
|
||||
3. Instead of running the application indefinitely, it performs a fixed number of render cycles (defaulting to 40 in `Benchmark::run`).
|
||||
4. For each cycle, it precisely measures the time taken to `render` your root component.
|
||||
5. After all cycles, it clears the screen and returns a `BenchmarkResult` containing statistics.
|
||||
|
||||
## `BenchmarkResult`
|
||||
|
||||
The `BenchmarkResult` struct holds the collected performance metrics:
|
||||
|
||||
```rust
|
||||
pub struct BenchmarkResult {
|
||||
pub average: u128, // Average render time in microseconds
|
||||
pub min: u128, // Minimum render time in microseconds
|
||||
pub max: u128, // Maximum render time in microseconds
|
||||
pub total_render: u128, // Sum of all render times in microseconds
|
||||
pub total: u128, // Total time spent during the benchmark (including setup)
|
||||
}
|
||||
```
|
||||
|
||||
These values are typically in microseconds (`µs`).
|
||||
|
||||
## Basic Usage Example
|
||||
|
||||
Let's use the `simple_benchmark.rs` example to see the `Benchmark` engine in action:
|
||||
|
||||
```rust title="examples/simple_benchmark.rs"
|
||||
use osui::prelude::*;
|
||||
use std::sync::Arc; // Needed for Arc<Context>
|
||||
|
||||
pub fn main() {
|
||||
// 1. Create a Console engine instance.
|
||||
let console_engine = Console::new();
|
||||
|
||||
// 2. Wrap it with the Benchmark engine.
|
||||
let benchmark_engine = Benchmark::new(console_engine);
|
||||
|
||||
// 3. Run your application (or component) through the Benchmark engine.
|
||||
let benchmark_result = benchmark_engine.run(App {}).expect("Failed to run benchmark");
|
||||
|
||||
// 4. Print the results.
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
To run this example:
|
||||
|
||||
```bash
|
||||
cargo run --example simple_benchmark
|
||||
```
|
||||
|
||||
You will see output similar to:
|
||||
|
||||
```
|
||||
Avg: 1078 μs
|
||||
Min: 1078 μs
|
||||
Max: 1078 μs
|
||||
Tot: 43120 μs
|
||||
Tot Render: 43120 μs
|
||||
```
|
||||
*(Note: Actual values will vary based on your system and terminal emulator.)*
|
||||
|
||||
## Advanced Usage: Benchmarking Complex Scenarios
|
||||
|
||||
The `benchmark.rs` example demonstrates how to benchmark nested components and iterate through different complexity levels. This is useful for identifying performance bottlenecks in specific UI patterns.
|
||||
|
||||
```rust title="examples/benchmark.rs"
|
||||
use osui::prelude::*;
|
||||
use std::collections::HashMap; // Needed for HashMap
|
||||
|
||||
pub fn main() {
|
||||
let engine = Arc::new(Benchmark::new(Console::new())); // Wrap Console in Benchmark, then Arc it.
|
||||
|
||||
let mut benchmark_results: HashMap<(usize, usize), BenchmarkResult> = HashMap::new();
|
||||
|
||||
// Iterate through different levels of nesting (n) and iterations (i)
|
||||
for i in 0..15 {
|
||||
for n in 0..15 {
|
||||
let res = {
|
||||
let mut results = Vec::with_capacity(6);
|
||||
|
||||
// Run each specific benchmark configuration multiple times (e.g., 6)
|
||||
// to get more consistent results, then pick the median (index 3 after sort).
|
||||
for _ in 0..6 {
|
||||
results.push(
|
||||
engine
|
||||
.run(App {
|
||||
n: n * 72, // Scale nesting depth
|
||||
i: i * 72, // Scale iteration count (for loops)
|
||||
})
|
||||
.expect("Failed to run engine"),
|
||||
);
|
||||
}
|
||||
|
||||
results.sort_by_key(|r| r.total_render); // Sort by total render time
|
||||
results[3].clone() // Take the median result
|
||||
};
|
||||
|
||||
benchmark_results.insert((i, n as usize), res);
|
||||
}
|
||||
}
|
||||
|
||||
// Output results in CSV format
|
||||
println!("Iterx72,Nestingx72,Time µs");
|
||||
for ((i, n), bench) in benchmark_results.iter() {
|
||||
println!("{i},{n},{}", bench.total_render);
|
||||
}
|
||||
}
|
||||
|
||||
// A recursive component that creates nested children and loops
|
||||
#[component]
|
||||
fn App(cx: &Arc<Context>, n: usize, i: usize) -> View {
|
||||
let n = n.clone(); // Clone props for closure (necessary for #[component] macro)
|
||||
let i = i.clone();
|
||||
|
||||
if n == 0 {
|
||||
// Base case: deepest level, render a simple string
|
||||
rsx! {
|
||||
"Hello, world!"
|
||||
}
|
||||
.view(&cx)
|
||||
} else {
|
||||
// Recursive case: create `i` number of children, each with reduced nesting `n-1`
|
||||
rsx! {
|
||||
@for _ in (0..i) { // Loop `i` times
|
||||
App { n: n - 1, i: 0 } // Create a nested App component
|
||||
}
|
||||
}
|
||||
.view(&cx)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This example:
|
||||
* Uses an `Arc<Benchmark>` to allow `run` to be called multiple times.
|
||||
* Iterates through different `n` (nesting depth) and `i` (number of children in a loop) values.
|
||||
* Runs each configuration multiple times and takes the median `total_render` to reduce noise.
|
||||
* Prints the results in CSV format, which can be easily imported into spreadsheet software for analysis (like the `benchmark.csv` in the repository).
|
||||
|
||||
## Interpreting Results
|
||||
|
||||
* **`average`, `min`, `max`**: Provide insight into the consistency of your rendering performance. A large difference between `min` and `max` might indicate inconsistencies or external factors affecting performance.
|
||||
* **`total_render`**: The sum of all individual render cycle times. This is often the most important metric for overall performance.
|
||||
* **`total`**: The total time for the benchmark process, including setup. This is less about rendering speed and more about the overhead of the benchmark itself.
|
||||
|
||||
When analyzing benchmarks, look for:
|
||||
* **Linear vs. Non-linear Scaling**: How does `total_render` increase as you increase nesting depth or the number of components? Ideally, it should scale linearly.
|
||||
* **Bottlenecks**: Can you isolate which components or `rsx!` patterns (e.g., complex loops, many dynamic scopes) contribute most to render time?
|
||||
* **Regression**: Use benchmarks in your CI/CD pipeline to detect performance regressions introduced by new code.
|
||||
|
||||
By leveraging OSUI's `Benchmark` engine, you can gain valuable insights into your TUI application's performance characteristics and make data-driven decisions for optimization.
|
||||
|
||||
**Next:** Learn how to customize OSUI by [Implementing a Custom Engine](./01-customizing-the-engine.md).
|
||||
@@ -0,0 +1,260 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
title: Customizing the Engine
|
||||
---
|
||||
|
||||
# Customizing the Engine
|
||||
|
||||
OSUI's `Engine` and `CommandExecutor` traits are designed to be highly extensible. While the `Console` engine provides `crossterm`-based terminal rendering, you might want to create a custom engine for various reasons:
|
||||
|
||||
* **Different Rendering Backend**: Render to a graphical window (e.g., using `minifb` or `pixels`), a web canvas, or a specific hardware display.
|
||||
* **Headless Testing**: Create a dummy engine that doesn't render anything but processes all commands and component logic, useful for fast unit or integration tests.
|
||||
* **Logging/Debugging**: An engine that logs all `DrawInstruction`s to a file for analysis.
|
||||
* **Specialized Behavior**: Implement custom render loops, input handling, or command processing.
|
||||
|
||||
This guide will walk you through the process of implementing your own `Engine` and `CommandExecutor`.
|
||||
|
||||
## Implementing `CommandExecutor`
|
||||
|
||||
First, let's define a custom `CommandExecutor`. This trait is responsible for processing commands issued by components (e.g., `cx.stop()`).
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::{
|
||||
any::Any,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
// Define a custom command
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CustomCommand(pub String);
|
||||
|
||||
impl Command for CustomCommand {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MyCustomExecutor {
|
||||
running: Mutex<bool>,
|
||||
received_commands: Mutex<Vec<CustomCommand>>,
|
||||
}
|
||||
|
||||
impl MyCustomExecutor {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self {
|
||||
running: Mutex::new(true),
|
||||
received_commands: Mutex::new(Vec::new()),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn stop_engine(&self) -> crate::Result<()> {
|
||||
*self.running.lock()? = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_status(&self) -> bool {
|
||||
*self.running.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn get_received_commands(&self) -> Vec<CustomCommand> {
|
||||
self.received_commands.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandExecutor for MyCustomExecutor {
|
||||
fn execute_command(&self, command: &Arc<dyn Command>) -> crate::Result<()> {
|
||||
let command_any = command.as_any();
|
||||
|
||||
// Handle built-in Stop command
|
||||
if let Some(commands::Stop) = command_any.downcast_ref::<commands::Stop>() {
|
||||
println!("MyCustomExecutor: Received Stop command.");
|
||||
return self.stop_engine();
|
||||
}
|
||||
|
||||
// Handle our custom command
|
||||
if let Some(custom_cmd) = command_any.downcast_ref::<CustomCommand>() {
|
||||
println!("MyCustomExecutor: Received CustomCommand: {:?}", custom_cmd);
|
||||
self.received_commands.lock().unwrap().push(custom_cmd.clone());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("MyCustomExecutor: Unhandled command.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Implementing `Engine`
|
||||
|
||||
Now, let's create a simple "headless" engine that doesn't draw to the terminal but just logs rendering events.
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct MyHeadlessEngine {
|
||||
executor: Arc<MyCustomExecutor>,
|
||||
log_output: Mutex<Vec<String>>,
|
||||
}
|
||||
|
||||
impl MyHeadlessEngine {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
executor: MyCustomExecutor::new(),
|
||||
log_output: Mutex::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to log messages
|
||||
fn log(&self, msg: &str) {
|
||||
self.log_output.lock().unwrap().push(msg.to_string());
|
||||
}
|
||||
|
||||
pub fn get_log(&self) -> Vec<String> {
|
||||
self.log_output.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Engine for MyHeadlessEngine {
|
||||
fn run<C: ComponentImpl + 'static>(&self, component: C) -> crate::Result<()> {
|
||||
self.log("Engine: Initializing component...");
|
||||
let cx = self.init(component);
|
||||
|
||||
while self.executor.get_status() {
|
||||
self.log("Engine: Starting render cycle...");
|
||||
self.render(&cx);
|
||||
self.log("Engine: Render cycle complete. Delaying...");
|
||||
self.render_delay(); // Use default delay or implement custom
|
||||
}
|
||||
|
||||
self.log("Engine: Application stopped.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init<C: ComponentImpl + 'static>(&self, component: C) -> Arc<Context> {
|
||||
// Perform any setup needed for your custom engine
|
||||
self.log("Engine: Component initialized.");
|
||||
let cx = Context::new(component, self.executor.clone());
|
||||
cx.refresh(); // Initial render of the component
|
||||
cx
|
||||
}
|
||||
|
||||
fn render(&self, cx: &Arc<Context>) {
|
||||
let area = Area { x: 0, y: 0, width: 80, height: 24 }; // Define a virtual screen size
|
||||
let draw_ctx = self.render_view(&area, &cx.get_view());
|
||||
self.draw_context(&draw_ctx);
|
||||
}
|
||||
|
||||
// No actual delay for headless, or keep default for testing loop speed
|
||||
fn render_delay(&self) {
|
||||
// crate::sleep(16); // Uncomment for actual delay
|
||||
}
|
||||
|
||||
fn render_view(&self, area: &Area, view: &View) -> DrawContext {
|
||||
self.log(&format!("Engine: Rendering view in area: {:?}", area));
|
||||
let mut context = DrawContext::new(area.clone());
|
||||
view(&mut context); // Execute the View closure to populate DrawContext
|
||||
context
|
||||
}
|
||||
|
||||
fn draw_context(&self, ctx: &DrawContext) {
|
||||
self.log(&format!("Engine: Drawing context with {} instructions.", ctx.drawing.len()));
|
||||
for inst in &ctx.drawing {
|
||||
match inst {
|
||||
DrawInstruction::Text(point, text) => self.log(&format!(" Draw Text at {:?}: '{}'", point, text)),
|
||||
DrawInstruction::View(area, view) => {
|
||||
self.log(&format!(" Draw Child View in area: {:?}", area));
|
||||
self.draw_context(&self.render_view(area, view)); // Recursively render child views
|
||||
},
|
||||
DrawInstruction::Child(point, child_ctx) => {
|
||||
self.log(&format!(" Draw Child DrawContext at {:?}.", point));
|
||||
self.draw_context(child_ctx); // Recursively draw child contexts
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn executor(&self) -> Arc<dyn CommandExecutor> {
|
||||
self.executor.clone()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Your Custom Engine
|
||||
|
||||
```rust
|
||||
use osui::prelude::*;
|
||||
use std::sync::Arc;
|
||||
|
||||
// (Include MyHeadlessEngine, MyCustomExecutor, CustomCommand definitions here)
|
||||
|
||||
#[component]
|
||||
fn MyApp(cx: &Arc<Context>) -> View {
|
||||
let counter = use_state(0);
|
||||
|
||||
use_effect(
|
||||
{
|
||||
let cx = cx.clone();
|
||||
let counter = counter.clone();
|
||||
move || {
|
||||
// Periodically increment counter and emit custom command
|
||||
loop {
|
||||
sleep(200);
|
||||
let new_val = *counter.get() + 1;
|
||||
counter.set(new_val);
|
||||
if new_val >= 3 {
|
||||
cx.execute(CustomCommand(format!("Counter reached {}", new_val))).expect("Cmd failed");
|
||||
cx.stop().expect("Stop failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
&[], // Run once on mount
|
||||
);
|
||||
|
||||
rsx! {
|
||||
format!("Counter value: {}", counter.get_dl())
|
||||
}.view(&cx)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let my_engine = MyHeadlessEngine::new();
|
||||
let executor = my_engine.executor.clone(); // Get a reference to the executor
|
||||
|
||||
my_engine.run(MyApp {}).expect("Failed to run custom engine");
|
||||
|
||||
println!("\n--- Engine Log ---");
|
||||
for line in my_engine.get_log() {
|
||||
println!("{}", line);
|
||||
}
|
||||
|
||||
println!("\n--- Received Commands ---");
|
||||
for cmd in executor.get_received_commands() {
|
||||
println!("{:?}", cmd);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Explanation:
|
||||
|
||||
1. **`MyCustomExecutor`**: Implements `CommandExecutor`. It handles the built-in `commands::Stop` and our new `CustomCommand`. It also keeps a log of received custom commands for verification.
|
||||
2. **`MyHeadlessEngine`**: Implements `Engine`.
|
||||
* It takes `MyCustomExecutor` as its command executor.
|
||||
* `run` method establishes a basic loop that continues as long as `executor.get_status()` is `true`.
|
||||
* `render` orchestrates the `render_view` and `draw_context` calls.
|
||||
* `render_view` executes the component `View` and collects `DrawInstruction`s.
|
||||
* `draw_context` iterates through `DrawInstruction`s, logging them instead of actually drawing to a terminal. It recursively handles `DrawInstruction::View` and `DrawInstruction::Child`.
|
||||
3. **`MyApp` Component**:
|
||||
* Uses `use_state` for a counter.
|
||||
* Uses `use_effect` to periodically increment the counter.
|
||||
* When the counter reaches 3, it `execute`s our `CustomCommand` and then `stop()`s the engine.
|
||||
4. **`main` Function**:
|
||||
* Instantiates `MyHeadlessEngine`.
|
||||
* Calls `my_engine.run(MyApp {})`.
|
||||
* After the engine stops, it prints the internal log and received commands from the executor, allowing you to verify that component logic and commands were processed correctly.
|
||||
|
||||
By following this pattern, you can integrate OSUI's powerful component and state management system with virtually any rendering or execution environment you desire.
|
||||
|
||||
**Next:** Dive into the internals of OSUI's macro system in [Internals: Macros](./02-internals-macros.md).
|
||||
@@ -0,0 +1,131 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
title: Internals: Macros
|
||||
---
|
||||
|
||||
# Internals: Macros
|
||||
|
||||
OSUI heavily relies on procedural macros to provide its ergonomic, declarative syntax. The `osui-macros` crate contains the logic for the `#[component]` attribute macro and the `rsx!` function macro. Understanding how these macros work under the hood gives you a deeper insight into OSUI's architecture and capabilities.
|
||||
|
||||
## Introduction to Procedural Macros
|
||||
|
||||
Procedural macros are functions that operate on the Rust syntax tree (Abstract Syntax Tree or AST) during compilation. They receive `TokenStream`s as input and produce `TokenStream`s as output, effectively transforming your code. OSUI's macros leverage the following crates:
|
||||
|
||||
* **`syn`**: A parser for Rust's syntax tree. It allows macros to parse input `TokenStream`s into structured Rust AST types (like `ItemFn`, `Expr`, `Path`, etc.).
|
||||
* **`quote`**: A quasiquoting library that makes it easy to generate Rust code (as `TokenStream`s) from AST fragments.
|
||||
* **`proc-macro2`**: Provides types like `TokenStream` and `Ident` that are compatible with `syn` and `quote`, enabling ergonomic manipulation of tokens.
|
||||
|
||||
## `#[component]` Attribute Macro (`macros/src/lib.rs`)
|
||||
|
||||
The `#[component]` macro transforms a Rust function into an OSUI component struct.
|
||||
|
||||
### Input
|
||||
|
||||
It takes an `ItemFn` (the parsed function definition) as input.
|
||||
|
||||
```rust
|
||||
// Original function in user's code
|
||||
#[component]
|
||||
pub fn MyComponent(cx: &Arc<Context>, prop1: &String, prop2: &i32) -> View {
|
||||
// ... function body ...
|
||||
}
|
||||
```
|
||||
|
||||
### Core Logic
|
||||
|
||||
1. **Parse Function Signature**:
|
||||
* It extracts the function's name (`MyComponent`).
|
||||
* It validates the first argument `cx: &Arc<Context>`.
|
||||
* It iterates through the remaining arguments (`prop1: &String`, `prop2: &i32`), which become the component's props.
|
||||
2. **Generate Component Struct**: For each prop parameter, it determines the *owned* type (e.g., `&String` becomes `String`, `&i32` becomes `i32`). It then uses `quote!` to generate a new struct:
|
||||
```rust
|
||||
pub struct MyComponent {
|
||||
pub prop1: String, // Owned types
|
||||
pub prop2: i32,
|
||||
}
|
||||
```
|
||||
3. **Implement `ComponentImpl`**: It then generates an `impl ComponentImpl for MyComponent` block. The `call` method of this trait:
|
||||
* Takes `&self` and `cx: &Arc<Context>`.
|
||||
* Internally calls a generated `Self::component` method (which is your original function's body).
|
||||
* Passes `cx` and references (`&self.prop1`, `&self.prop2`) to the stored props from the generated struct.
|
||||
```rust
|
||||
impl MyComponent {
|
||||
// This is your original function, renamed and wrapped
|
||||
pub fn component(cx: &Arc<Context>, prop1: &str, prop2: &i32) -> View { /* ... body ... */ }
|
||||
}
|
||||
|
||||
impl ComponentImpl for MyComponent {
|
||||
fn call(&self, cx: &Arc<Context>) -> View {
|
||||
Self::component(cx, &self.prop1, &self.prop2) // Pass references to owned props
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
The macro replaces the original function with the generated struct, its `component` method, and the `ComponentImpl` implementation. This transformation makes `MyComponent` a valid OSUI component that can be instantiated with props in `rsx!`.
|
||||
|
||||
## `rsx!` Function Macro (`macros/src/parse.rs` & `macros/src/emit.rs`)
|
||||
|
||||
The `rsx!` macro is more complex, involving a two-step process: parsing the custom syntax and then emitting standard Rust code.
|
||||
|
||||
### 1. Parsing (`macros/src/parse.rs`)
|
||||
|
||||
The `parse` module defines an AST (Abstract Syntax Tree) for the `rsx!` syntax.
|
||||
|
||||
* **`RsxRoot`**: The top-level container, holding a vector of `RsxNode`s.
|
||||
* **`RsxNode`**: An enum representing different types of nodes in the `rsx!` tree:
|
||||
* `Text(LitStr)`: For `"Hello"` literals.
|
||||
* `Expr(Expr)`: For `@{some_expression}` blocks.
|
||||
* `Component { path: Path, props: Vec<RsxProp>, children: Vec<RsxNode> }`: For `MyComponent { prop: val, ... }`.
|
||||
* `Mount(Ident)`: For `!my_mount_hook`.
|
||||
* `If { deps: Vec<Dep>, cond: Expr, children: Vec<RsxNode> }`: For `@if condition { ... }`.
|
||||
* `For { deps: Vec<Dep>, pat: Pat, expr: Expr, children: Vec<RsxNode> }`: For `@for item in items { ... }`.
|
||||
* **`RsxProp`**: Represents a `name: value` pair for component props.
|
||||
* **`Dep`**: Represents a dependency for dynamic blocks (`%my_state as my_alias`).
|
||||
|
||||
The `parse::RsxRoot::parse` method uses `syn`'s `ParseStream` to tokenize the `rsx!` input and build this AST. It intelligently differentiates between text, expressions, component names, and control flow keywords (`@if`, `@for`, `!`).
|
||||
|
||||
### 2. Emitting (`macros/src/emit.rs`)
|
||||
|
||||
The `emit` module takes the parsed `RsxRoot` AST and converts it into a `TokenStream` of standard Rust code that constructs `osui::frontend::Rsx` objects.
|
||||
|
||||
* **`emit_rsx(root: RsxRoot)`**: The entry point, which initializes an `osui::frontend::Rsx` object and then iterates through the `root.nodes`.
|
||||
* **`emit_node_scope(node: &RsxNode)`**: For each `RsxNode`, it generates code that calls the appropriate `Rsx` builder method:
|
||||
* **`RsxNode::Text`**: Emits `r.static_scope(move |scope| { scope.view(...) });` which draws text.
|
||||
* **`RsxNode::Expr`**: Emits `r.child(expression);`.
|
||||
* **`RsxNode::Component`**: Emits `r.static_scope(move |scope| { scope.child(ComponentName { props: ... }, None); });`. If children are present, they are recursively emitted into a nested `Rsx` object and passed as the `children` prop.
|
||||
* **`RsxNode::Mount`**: Emits `mount_hook_instance.mount();`.
|
||||
* **`RsxNode::If`**: Emits `r.dynamic_scope(move |scope| { if condition { ... } else { ... } }, dependencies);`. The `dependencies` are converted to `Vec<Arc<dyn HookDependency>>`.
|
||||
* **`RsxNode::For`**: Similar to `If`, emits `r.dynamic_scope(move |scope| { for pattern in expr { ... } }, dependencies);`.
|
||||
|
||||
### Output
|
||||
|
||||
The `rsx!` macro produces a `TokenStream` that looks something like this (simplified):
|
||||
|
||||
```rust
|
||||
// For: rsx! { "Hello" MyComponent { prop: value } }
|
||||
{
|
||||
let mut r = osui::frontend::Rsx::new();
|
||||
r.static_scope(move |scope| {
|
||||
scope.view(std::sync::Arc::new(move |ctx| {
|
||||
ctx.draw_text(osui::render::Point { x: 0, y: 0 }, &format!("Hello"))
|
||||
}));
|
||||
});
|
||||
r.static_scope(move |scope| {
|
||||
scope.child(
|
||||
MyComponent { prop: value.to_string() }, // Note: Prop value converted to owned type
|
||||
None,
|
||||
);
|
||||
});
|
||||
r
|
||||
}
|
||||
```
|
||||
|
||||
This generated code, when compiled, constructs the `osui::frontend::Rsx` object that OSUI's runtime can then interpret to build the component tree and render the UI.
|
||||
|
||||
## Summary
|
||||
|
||||
The `osui-macros` crate plays a pivotal role in shaping OSUI's developer experience. `#[component]` streamlines component definition, while `rsx!` provides a powerful, declarative way to compose UIs by transforming custom syntax into efficient runtime calls. These macros are complex but essential for creating a modern, React-like development flow in a TUI environment.
|
||||
|
||||
**Next:** Learn how you can contribute to the OSUI project in [Contributing](./03-contributing.md).
|
||||
@@ -0,0 +1,115 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
title: Contributing
|
||||
---
|
||||
|
||||
# Contributing to OSUI
|
||||
|
||||
We welcome and appreciate contributions from the community! Whether it's reporting bugs, suggesting features, improving documentation, or submitting code, your help makes OSUI better for everyone.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
### 1. Report Bugs
|
||||
|
||||
If you find a bug, please open an issue on our [GitHub repository](https://github.com/osui-rs/osui/issues). When reporting a bug, please include:
|
||||
|
||||
* A clear and concise description of the bug.
|
||||
* Steps to reproduce the behavior.
|
||||
* Expected behavior.
|
||||
* Actual behavior.
|
||||
* Your OSUI version, Rust version, and operating system.
|
||||
* Any relevant code snippets or error messages.
|
||||
|
||||
### 2. Suggest Features
|
||||
|
||||
Have an idea for a new feature or improvement? Feel free to open an issue on GitHub to discuss it. Please provide:
|
||||
|
||||
* A clear and concise description of the proposed feature.
|
||||
* Why you think it would be valuable to OSUI.
|
||||
* Any potential use cases or examples.
|
||||
|
||||
### 3. Improve Documentation
|
||||
|
||||
Good documentation is vital for any library. If you find errors, omissions, or areas that could be explained more clearly in our docs, please consider:
|
||||
|
||||
* Opening an issue to point out the specific area.
|
||||
* Submitting a pull request with your suggested improvements.
|
||||
|
||||
### 4. Contribute Code
|
||||
|
||||
If you'd like to contribute code, here's the general workflow:
|
||||
|
||||
#### Fork the Repository
|
||||
|
||||
First, fork the [osui-rs/osui](https://github.com/osui-rs/osui) repository to your own GitHub account.
|
||||
|
||||
#### Clone Your Fork
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/osui.git
|
||||
cd osui
|
||||
```
|
||||
|
||||
#### Create a New Branch
|
||||
|
||||
Create a new branch for your feature or bug fix. Use a descriptive name:
|
||||
|
||||
```bash
|
||||
git checkout -b feature/my-awesome-feature
|
||||
# or
|
||||
git checkout -b bugfix/fix-rendering-issue
|
||||
```
|
||||
|
||||
#### Make Your Changes
|
||||
|
||||
* Write clean, idiomatic Rust code.
|
||||
* Follow existing code style and conventions.
|
||||
* Add comments where necessary to explain complex logic.
|
||||
* **Write Tests**: If you're adding new features or fixing bugs, please include appropriate unit and/or integration tests to cover your changes.
|
||||
* **Update Documentation**: If your changes affect the public API or add new functionality, please update the relevant documentation files.
|
||||
|
||||
#### Run Tests
|
||||
|
||||
Before submitting a pull request, ensure all existing tests pass and your new tests pass:
|
||||
|
||||
```bash
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
#### Format and Lint
|
||||
|
||||
Make sure your code is formatted correctly and passes lint checks:
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo clippy --all-targets --all-features
|
||||
```
|
||||
|
||||
#### Commit Your Changes
|
||||
|
||||
Commit your changes with clear and concise commit messages. A good commit message explains *what* changed and *why*.
|
||||
|
||||
```bash
|
||||
git commit -m "feat: Add new awesome feature"
|
||||
# or
|
||||
git commit -m "fix: Resolve rendering issue on Windows"
|
||||
```
|
||||
|
||||
#### Push to Your Fork
|
||||
|
||||
```bash
|
||||
git push origin feature/my-awesome-feature
|
||||
```
|
||||
|
||||
#### Create a Pull Request
|
||||
|
||||
Go to the [osui-rs/osui](https://github.com/osui-rs/osui) repository on GitHub and open a new pull request.
|
||||
* Provide a clear title and description for your pull request.
|
||||
* Reference any related issues (e.g., "Fixes #123" or "Closes #456").
|
||||
* The project maintainers will review your PR, provide feedback, and work with you to get it merged.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](https://github.com/osui-rs/osui/blob/main/CODE_OF_CONDUCT.md). By participating in this project, you agree to abide by its terms.
|
||||
|
||||
Thank you for considering contributing to OSUI! Your efforts help foster a vibrant and robust TUI ecosystem in Rust.
|
||||
Reference in New Issue
Block a user