Reactive State Management

Orlando's Signal and Stream types (Rust API) provide reactive primitives for state management with automatic change propagation.

Signals: Derived State

Signals represent time-varying values. When a source signal changes, all derived signals update automatically.

Temperature Converter

#![allow(unused)]
fn main() {
use orlando_transducers::signal::Signal;

let celsius = Signal::new(0.0_f64);
let fahrenheit = celsius.map(|c| c * 9.0 / 5.0 + 32.0);
let kelvin = celsius.map(|c| c + 273.15);

assert_eq!(*fahrenheit.get(), 32.0);
assert_eq!(*kelvin.get(), 273.15);

celsius.set(100.0);
assert_eq!(*fahrenheit.get(), 212.0);   // auto-updated
assert_eq!(*kelvin.get(), 373.15);      // auto-updated
}

Shopping Cart

#![allow(unused)]
fn main() {
use orlando_transducers::signal::Signal;

let items = Signal::new(vec![
    ("Widget", 9.99),
    ("Gadget", 24.99),
]);

let subtotal = items.map(|items| {
    items.iter().map(|(_, price)| price).sum::<f64>()
});

let tax_rate = Signal::new(0.08);

let total = subtotal.combine(&tax_rate, |sub, rate| {
    sub * (1.0 + rate)
});

assert_eq!(*subtotal.get(), 34.98);
// total = 34.98 * 1.08 = 37.7784

// Add an item
items.update(|mut items| {
    items.push(("Doohickey", 14.99));
    items
});
// subtotal, total auto-update
}

Combining Multiple Signals

#![allow(unused)]
fn main() {
let width = Signal::new(800_u32);
let height = Signal::new(600_u32);

let aspect_ratio = width.combine(&height, |w, h| {
    *w as f64 / *h as f64
});

let resolution = width.combine(&height, |w, h| {
    format!("{}x{}", w, h)
});

assert_eq!(*resolution.get(), "800x600");

width.set(1920);
height.set(1080);
assert_eq!(*resolution.get(), "1920x1080");
}

Streams: Event Processing

Streams handle discrete events with transformation pipelines.

Click Counter

#![allow(unused)]
fn main() {
use orlando_transducers::signal::Signal;
use orlando_transducers::stream::Stream;

let clicks = Stream::new();
let counter = Signal::new(0_i32);

// Bridge stream events into signal state
counter.fold(&clicks, 0, |count, _: &()| count + 1);

clicks.emit(());
clicks.emit(());
clicks.emit(());
assert_eq!(*counter.get(), 3);
}

Event Filtering

#![allow(unused)]
fn main() {
use orlando_transducers::stream::Stream;

let events = Stream::new();

// Only process error events
let errors = events.filter(|e: &Event| e.level == Level::Error);
errors.subscribe(|e| {
    eprintln!("ERROR: {}", e.message);
});

// Only process first 100 events
let limited = events.take(100);
limited.subscribe(|e| {
    log_event(e);
});
}

Stream Merging

#![allow(unused)]
fn main() {
use orlando_transducers::stream::Stream;

let keyboard = Stream::new();
let mouse = Stream::new();

// Merge into a unified input stream
let input = keyboard.merge(&mouse);
input.subscribe(|event| {
    handle_input(event);
});

keyboard.emit(InputEvent::KeyPress('a'));
mouse.emit(InputEvent::Click(100, 200));
// Both arrive at the merged subscriber
}

Transform Pipeline on Stream

#![allow(unused)]
fn main() {
let raw_messages = Stream::new();

// Build a processing pipeline on the stream
let processed = raw_messages
    .map(|msg: String| msg.trim().to_lowercase())
    .filter(|msg: &String| !msg.is_empty());

processed.subscribe(|msg| {
    println!("Processed: {}", msg);
});

raw_messages.emit("  Hello World  ".into());
// prints: Processed: hello world
}

Stream-Signal Bridge: .fold()

The .fold() method is the key bridge between discrete events (Stream) and continuous state (Signal).

Running Average

#![allow(unused)]
fn main() {
use orlando_transducers::signal::Signal;
use orlando_transducers::stream::Stream;

let measurements = Stream::new();
let stats = Signal::new((0.0_f64, 0_u32)); // (sum, count)

stats.fold(&measurements, (0.0, 0), |state, value: &f64| {
    (state.0 + value, state.1 + 1)
});

let average = stats.map(|(sum, count)| {
    if count > 0 { sum / count as f64 } else { 0.0 }
});

measurements.emit(10.0);
measurements.emit(20.0);
measurements.emit(30.0);
assert_eq!(*average.get(), 20.0);
}

State Machine

#![allow(unused)]
fn main() {
use orlando_transducers::signal::Signal;
use orlando_transducers::stream::Stream;

#[derive(Clone, Debug, PartialEq)]
enum AppState {
    Loading,
    Ready,
    Error(String),
}

let actions = Stream::new();
let state = Signal::new(AppState::Loading);

state.fold(&actions, AppState::Loading, |current, action: &Action| {
    match (current, action) {
        (AppState::Loading, Action::DataLoaded) => AppState::Ready,
        (_, Action::Error(msg)) => AppState::Error(msg.clone()),
        (_, Action::Reset) => AppState::Loading,
        (state, _) => state,
    }
});

actions.emit(Action::DataLoaded);
assert_eq!(*state.get(), AppState::Ready);
}

Subscription Lifecycle

Subscriptions are automatically cleaned up when dropped:

#![allow(unused)]
fn main() {
let counter = Signal::new(0);

{
    let _sub = counter.subscribe(|val| {
        println!("Value: {}", val);
    });

    counter.set(1);  // prints: Value: 1
    counter.set(2);  // prints: Value: 2
}
// _sub dropped here, subscription is cleaned up

counter.set(3);  // no output - subscriber is gone
}

For streams:

#![allow(unused)]
fn main() {
let events = Stream::new();

let sub = events.subscribe(|e| handle(e));

// Explicitly unsubscribe when done
drop(sub);
}