Advanced Topics: Performance and Customization
This section covers more advanced aspects of OSUI, including performance considerations and how to further customize the library beyond basic usage.
Performance Optimization
OSUI is designed to be efficient for terminal UIs, but there are always considerations to keep in mind:
-
StaticWidgetvs.DynWidget:- Prefer
staticelements for unchanging content: If a part of your UI never changes (e.g., a fixed label, a decorative border), use thestatickeyword inrsx!orScreen::drawto createStaticWidgets. They have zero overhead for dependency tracking and re-evaluation. - Use
DynWidget(implicit withoutstatic) only where reactivity is needed:DynWidgets carry the overhead of checking dependencies and potentially rebuilding theirElementand components every frame. While efficient, unnecessaryDynWidgets can add up.
- Prefer
-
State Management (
State<T>):- Granularity of State: Try to keep state changes as granular as possible. If only a small part of your UI needs to react to a specific state, make only that minimal
DynWidgetdepend on it, rather than a large parent widget. This limits the scope of re-evaluation. - Avoid Holding Locks: When interacting with
State<T>, acquire theMutexGuardviastate.get()for the shortest possible duration. If you only need to read the value, considerstate.get_dl()which clones the value and doesn't hold the lock, reducing potential for contention.
- Granularity of State: Try to keep state changes as granular as possible. If only a small part of your UI needs to react to a specific state, make only that minimal
-
Extensions and Threads:
- Offload Blocking Operations: As seen with
InputExtensionandTickExtension, blocking I/O operations (like reading input orthread::sleep) should ideally be performed in separate threads. This prevents the main rendering loop from stalling, ensuring a smooth and responsive UI. - Minimize Work in Hooks: The
Extensionhooks (especiallyrender_widgetandafter_render_widget) are called frequently, often for every widget every frame. Keep the logic within these hooks as lightweight as possible. For heavy computation, consider delegating to a separate thread and communicating results back via aStatevariable or anEvent.
- Offload Blocking Operations: As seen with
-
Terminal Redraws:
- OSUI internally manages clearing and redrawing the necessary parts of the screen. However, complex UIs with many elements or frequent full-screen redraws can still be perceived as flickering or slow on some terminals.
- While not directly exposed as a user configurable option, OSUI's design aims to minimize redraws by only updating changed cells where possible, though the current implementation does a full clear and redraw each frame. This is a common pattern for many TUIs.
Customization
1. Custom Elements
As detailed in Creating Custom Widgets, the primary way to customize OSUI is by implementing the Element trait. This allows you to define completely new visual components or logical containers tailored to your application.
2. Custom Components
Beyond the built-in Transform and Style, you can define any arbitrary data or behavior as a Component using the component! macro. This is invaluable for:
- Marker Traits: Like
FocusedorAlwaysFocused, indicating a property without holding data. - Behavioral Data: Storing data related to a specific behavior, e.g.,
Velocity(x, y)for animation. - Event Handling: The
Handler<E>component allows attaching event-specific logic directly to widgets. - Custom Logic: Storing state specific to a widget's custom behavior that isn't part of its core
Elementdata.
3. Custom Extensions
The Extension trait provides the deepest level of customization by allowing you to inject global logic into the OSUI application lifecycle. Use extensions for:
- Global Event Handling: Listening to all events across the UI.
- System Integrations: Interacting with external systems (e.g., network, files, other libraries).
- Custom Layout Passes: Implementing alternative or additional layout algorithms.
- Debugging Tools: Injecting logging or visualization aids.
- Theming Systems: Implementing a global theming system that dynamically adjusts widget styles.
4. Cargo.toml Profiles
The Cargo.toml provides specific build profiles that can be customized for performance and debugging.
[profile.dev]
opt-level = 1 # Optimization level for development builds. `1` is a good balance.
debug = true # Include debug info.
debug-assertions = true # Enable runtime checks for debugging.
overflow-checks = true # Enable integer overflow checks.
lto = false # Link-time optimizations are off for faster compile times.
panic = 'unwind' # Panic unwinds the stack.
[profile.release]
opt-level = "z" # Optimize for size. Can also use `3` for max speed.
debug = false # No debug info.
debug-assertions = false # No runtime checks.
overflow-checks = false # No integer overflow checks.
lto = true # Enable link-time optimizations for max performance.
panic = 'abort' # Panic aborts the process immediately (can be faster).
codegen-units = 1 # Single codegen unit for max optimization (longer compile).
opt-level = "z"in release: This prioritizes binary size over raw speed. For TUI applications, small binary size can be desirable. You might change this toopt-level = 3for maximum performance, though the visual difference in most TUI apps might be negligible.lto = truein release: Link-time optimizations can significantly improve runtime performance by allowing the compiler to optimize across crate boundaries. This comes at the cost of longer release build times.panic = 'abort'in release: When a panic occurs in a release build, the program immediately terminates without unwinding the stack. This can sometimes lead to smaller binaries and slightly faster panic paths, but makes debugging panics harder.panic = 'unwind'(default fordev) is generally safer for debugging.
By understanding and leveraging these profiles, you can fine-tune the performance characteristics of your OSUI application.
5. no_rsx and no_elem Features
OSUI offers features that allow you to strip out parts of the library you don't use, primarily for reducing binary size if not for performance in all cases.
# Cargo.toml
[features]
no_rsx = [] # Disables the rsx! macro and its related frontend components.
no_elem = [] # Disables all built-in elements (Div, Flex, Input, etc.).
no_rsx: If you prefer to build your UI programmatically (usingWidget::new_static,Widget::new_dyn,WidgetLoad, etc.) instead of the declarativersx!macro, enabling this feature removes thersx!macro and thefrontendmodule. This can be useful for very small, custom-built applications or for library consumers who want to define their own UI construction layer.no_elem: If you intend to implement all your UI elements from scratch (e.g., you only needElement,Component,Screen, andExtensiontraits), enabling this feature removes all the built-in elements likeDiv,FlexRow,Input,Heading, andPaginator. This can drastically reduce the binary size if your application is entirely custom.
How to use features:
In your Cargo.toml:
[dependencies]
osui = { version = "0.1.1", features = ["no_rsx"] } # Or ["no_elem"], or both
Or on the command line:
cargo build --features no_rsx
cargo run --release --features "no_rsx no_elem"
These features offer a fine-grained control over the final binary, allowing you to tailor OSUI to your exact needs and potentially reduce its footprint in constrained environments.