# dbuff
[](https://crates.io/crates/dbuff)
[](https://opensource.org/license/lgpl-3-0)
[](https://github.com/jayson-lennon/dbuff)
Lock-free shared state for [ratatui](https://github.com/ratatui/ratatui) applications. UI threads read snapshots instantly while background workers apply updates asynchronously.
`dbuff` provides `SharedDomainData<D>` — an `ArcSwap`-backed double buffer. Reads via `.read()` return a guard that dereferences to `&D` without any lock. Writes go through a channel and are batched by a coalescing worker, so the UI never blocks. On top of that, `dbuff` ships composable async command chains, stream consumers, and a keyed task pool — all wired to write results back into your domain data.
## Concepts
The core idea: you run an async _command_ and provide a closure that says what to do with the _result_. The crate handles the lifecycle including spawning the task, awaiting the output, and calling your closure when it's done.
That closure is where you decide how to signal the result to readers. The most common approach is to write it into shared state via `SharedDomainData`, but you could also send it through a channel or anything else. The crate is generic over signalling mechanism via closure.
A _command_ is any type that implements the `Command` trait. The generic parameter is a services type — config, API clients, or anything the command needs. Use `()` if you don't need any:
```rust
struct FetchUser(i32);
#[async_trait::async_trait]
impl Command<ApiClient> for FetchUser {
type Output = User;
type Error = AppError;
async fn execute(self, client: ApiClient) -> Result<User, AppError> {
client.get_user(self.0).await
}
}
```
You then run the command and provide a closure for what to do with the result:
```text
```rust
let guard = domain.read();
println!("counter = {}", guard.counter);
```
Write state without blocking — closures are sent through a channel and batched:
```rust
## Commands
Implement the `Command` trait to define async operations. Each command declares its own `Output` and `Error` types:
```rust
struct Add(i32);
#[async_trait::async_trait]
impl Command<()> for Add {
type Output = i32;
type Error = CmdError;
async fn execute(self, _: ()) -> Result<i32, CmdError> {
Ok(self.0)
}
}
```
The second argument to `Command` is a services type. You can pass config, API clients, or any shared dependencies. Use `()` if you don't need any:
```rust
struct Greet(String);
#[async_trait::async_trait]
impl Command<AppServices> for Greet {
type Output = String;
type Error = CmdError;
async fn execute(self, services: AppServices) -> Result<String, CmdError> {
Ok(format!("{} {}", services.prefix, self.0))
}
}
```
Execute a command with `.bind(services, rt).exec(...)`:
```rust
domain.bind((), rt)
.exec(Add(10), |shared, v: &i32| shared.counter += *v)
.go_detach();
```
## Command Chains
Chain multiple commands with `.exec()` for sequential execution:
```rust
domain.bind((), rt)
.exec(Add(10), |shared, v: &i32| shared.total += *v)
.exec(Add(20), |shared, v: &i32| shared.total += *v)
.exec(Add(5), |shared, v: &i32| shared.total += *v)
.go();
```
### Pipelines with `.then()`
Use `.then()` to pass the previous command's output into the next. Build multi-step pipelines where each step feeds the one after it:
```rust
// ResolvePath -> ReadFile -> ParseConfig
domain.bind((), rt)
.exec(ResolvePath("config.toml".into()), |shared, path: &PathBuf| shared.config_path = path.clone())
.then(
|path: &PathBuf| ReadFile(path.clone()),
|shared, contents: &String| shared.raw_config = contents.clone(),
)
.then(
|contents: &String| ParseConfig(contents.clone()),
|shared, config: &Config| shared.config = config.clone(),
)
.go();
```
### Error handling
Chains short-circuit on the first error. Use `.on_error()` to capture it. The returned `ControlFlow` tells you whether the chain completed or was interrupted:
```rust
// A command that always fails
struct Fail;
#[async_trait::async_trait]
impl Command<()> for Fail {
type Output = ();
type Error = CmdError;
async fn execute(self, _: ()) -> Result<(), CmdError> {
Err(CmdError)
}
}
let handle = domain.bind((), rt)
.on_error(|err, shared| shared.error_log.push(format!("{err}")))
.exec(Add(10), |shared, v: &i32| shared.total += *v)
.exec_discard(Fail) // returns Err, causes short-circuit
.exec(Add(5), |shared, v: &i32| shared.total += *v) // skipped
.go();
let flow = handle.await.unwrap();
assert_eq!(flow, ControlFlow::Break);
```
## Status Tracking
Use `.tracked()` to store a command's `TaskStatus` directly in domain state. The resolved value lives inside the `TaskStatus`, so you don't need a separate field to hold the result:
```rust
#[derive(Clone, Default)]
struct AppData {
search_status: TaskStatus<Vec<String>>,
}
domain.bind((), rt)
.exec(SearchFiles("query".into()), |_, _| {})
.tracked(|shared, status| shared.search_status = status)
.go();
```
Then in your render loop, a single `domain.read()` covers loading, results, and errors:
```rust
match &domain.read().search_status {
TaskStatus::Idle => {}, // not started
TaskStatus::Pending => {}, // show spinner
TaskStatus::Resolved(results) => {}, // render results
TaskStatus::Error(e) => {}, // show error
TaskStatus::Aborted => {}, // show cancelled
}
```
## Examples
Only some of the features are mentioned here. The [`examples/`](./examples) directory contains detailed, runnable examples covering every feature.
## License
[LGPL-3.0-or-later](https://www.gnu.org/licenses/lgpl-3.0.en.html)