Skip to content
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ members = [
"examples/weather-mesh-demo/weather-hub",
"examples/weather-mesh-demo/weather-station-alpha",
"examples/weather-mesh-demo/weather-station-beta",
"examples/weather-mesh-demo/weather-station-gamma",
"examples/weather-mesh-demo/weather-station-gamma", "examples/hello-mailbox",
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
]
exclude = ["_external"]
resolver = "2"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ docker compose up
| --- | --- | --- |
| **SPMC Ring** | Bounded stream with independent consumers | Sensor telemetry, event logs |
| **SingleLatest** | Only the current value matters | Feature flags, config, UI state |
| **Mailbox** | Latest instruction wins | Device commands, actuation, RPC |
| [**Mailbox**](https://github.com/aimdb/aimdb/tree/main/examples/hello-mailbox) | Latest instruction wins | Device commands, actuation, RPC |
Comment thread
ggmaldo marked this conversation as resolved.
Outdated

- **Capabilities are unlocked by traits.** Implement `Streamable` to cross WASM/WebSocket/CLI boundaries, `Migratable` for typed schema evolution, `Observable` for monitoring, `Linkable` for wire-format connectors. Without the trait, the type system says no.
- **Connectors that ship today:** MQTT, KNX, WebSocket. Writing your own is one trait impl.
Expand Down
15 changes: 15 additions & 0 deletions examples/hello-mailbox/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "hello-mailbox"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "Hello Mailbox for issue #94"
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
publish = false

[dependencies]
aimdb-core = { path = "../../aimdb-core", features = ["std"] }
aimdb-tokio-adapter = { path = "../../aimdb-tokio-adapter", features = ["tokio-runtime"] }
aimdb-sync = { path = "../../aimdb-sync" }
tokio = { workspace = true, features = ["full"] }
serde = { workspace = true, features = ["derive"] }
rand = { version = "0.8", features = ["std_rng"] }
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
36 changes: 36 additions & 0 deletions examples/hello-mailbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# hello-mailbox: Mailbox buffer demo
The Mailbox buffer demo proves that only the last message is retained/read, in simple terms
It's like when you have a mailbox and only you can see the last letter.
Comment thread
ggmaldo marked this conversation as resolved.
Outdated

## When to use
This Mailbox buffer demo is useful in a variety of scenarios where you want to retain only the last message, for example:
- When you have a robotic arm that needs to execute a sequence of commands, and you only want to execute the last command, or even if the robot gets too much commands in a short period of time.

## How it works
The Mailbox buffer demo works by simulating a mailbox buffer that retains only the last message.
it runs 2 rounds:
- the first round sends 3 Colors and the MailBox only gets the last message.
- the second round sends 2 Colors and the MailBox only gets the last message.

## How to run
To run the demo, compile and run the `hello-mailbox` example, you need to go to the `aimdb/examples/hello-mailbox` directory and run:
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
```
cargo run -p hello-mailbox
```
**Expected output**
```
=== hello-mailbox: Mailbox buffer demo ===

Round 1
1. Firing three rapid commands BEFORE consumer exists: Red → Green → Blue
2. Consumer created AFTER the burst — reads once:
✓ Got: Blue ← only the latest survived
(Red and Green were overwritten before anyone could read them)

Round 2
1. Firing two rapid commands BEFORE consumer exists: Red → Green
2. Consumer created AFTER the burst — reads once:
✓ Got: Green ← only the latest survived (Green)
3. Shutting down...
✓ Done.
```
87 changes: 87 additions & 0 deletions examples/hello-mailbox/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use aimdb_core::{buffer::BufferCfg, AimDbBuilder};
use aimdb_sync::AimDbBuilderSyncExt;
use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt};
use serde::{Deserialize, Serialize};
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
use std::sync::Arc;
use std::thread;
use std::time::Duration;

// Enum of colors for the LED
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
enum Color {
Red,
Green,
Blue,
}

// Struct representing the LED state
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
Comment thread
ggmaldo marked this conversation as resolved.
Outdated
struct Led {
color: Color,
}

// Main function
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== hello-mailbox: Mailbox buffer demo ===\n");

// configuration
let adapter = Arc::new(TokioAdapter);
let mut builder = AimDbBuilder::new().runtime(adapter);

builder.configure::<Led>("actuator.led", |reg| {
reg.buffer(BufferCfg::Mailbox);
});

let handle = builder.attach()?;
let producer = handle.producer::<Led>("actuator.led")?;

{
// Produce quickly BEFORE creating the consumer
println!(" Round 1 ");
println!("1. Firing three rapid commands BEFORE consumer exists: Red → Green → Blue");
producer.set(Led { color: Color::Red })?;
producer.set(Led {
color: Color::Green,
})?;
producer.set(Led { color: Color::Blue })?;
thread::sleep(Duration::from_millis(100));

// Now we create the consumer — it will only see the last value in the Mailbox
println!("2. Consumer created AFTER the burst — reads once:");
let consumer = handle.consumer::<Led>("actuator.led")?;
thread::sleep(Duration::from_millis(100));

match consumer.try_get() {
Ok(msg) => println!(" ✓ Got: {:?} ← only the latest survived", msg.color),
Err(_) => println!(" (mailbox was already empty)"),
}

println!(" (Red and Green were overwritten before anyone could read them)\n");
}

println!(" Round 2 ");
println!("1. Firing two rapid commands BEFORE consumer exists: Red → Green");
// Create the color burst again
producer.set(Led { color: Color::Red })?;
producer.set(Led {
color: Color::Green,
})?;
thread::sleep(Duration::from_millis(100));

println!("2. Consumer created AFTER the burst — reads once:");
let consumer2 = handle.consumer::<Led>("actuator.led")?;
thread::sleep(Duration::from_millis(100));

match consumer2.try_get() {
Ok(msg) => println!(
" ✓ Got: {:?} ← only the latest survived (Green)",
msg.color
),
Err(_) => println!(" (mailbox was already empty)"),
}

println!("3. Shutting down...");
handle.detach()?;
println!(" ✓ Done.");
Ok(())
}