use std::time::Duration;
use acton_reactive::ipc::{socket_exists, IpcConfig};
use acton_reactive::prelude::*;
use tracing_subscriber::EnvFilter;
#[acton_message(ipc)]
struct PriceUpdate {
symbol: String,
price: f64,
change: f64,
timestamp_ms: u64,
}
#[acton_message(ipc)]
struct TradeExecuted {
trade_id: String,
symbol: String,
quantity: u32,
price: f64,
side: String,
}
#[acton_message(ipc)]
struct SystemStatus {
status: String,
message: String,
timestamp_ms: u64,
}
#[acton_message]
#[derive(Default)]
struct BroadcastPrices;
#[acton_message]
struct BroadcastTrade {
symbol: String,
quantity: u32,
price: f64,
side: String,
}
fn timestamp_millis() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| u64::try_from(d.as_millis()).unwrap_or(u64::MAX))
}
#[derive(Debug, Clone)]
struct PriceFeedState {
prices: std::collections::HashMap<String, f64>,
update_count: u64,
}
impl Default for PriceFeedState {
fn default() -> Self {
let mut prices = std::collections::HashMap::new();
prices.insert("AAPL".to_string(), 175.50);
prices.insert("GOOGL".to_string(), 142.30);
prices.insert("MSFT".to_string(), 378.90);
prices.insert("AMZN".to_string(), 178.25);
Self {
prices,
update_count: 0,
}
}
}
async fn create_price_feed_actor(runtime: &mut ActorRuntime) -> ActorHandle {
let mut price_feed = runtime.new_actor_with_name::<PriceFeedState>("price_feed".to_string());
price_feed.mutate_on::<BroadcastPrices>(|actor, _envelope| {
let broker = actor.broker().clone();
actor.model.update_count += 1;
let seed = actor.model.update_count;
let updates: Vec<PriceUpdate> = actor
.model
.prices
.iter_mut()
.enumerate()
.map(|(i, (symbol, price))| {
let combined = seed.wrapping_add(i as u64);
let direction = if combined.is_multiple_of(3) {
-1.0
} else {
1.0
};
let magnitude = f64::from((combined % 100) as u32) / 100.0;
let change = direction * magnitude;
*price += change;
PriceUpdate {
symbol: symbol.clone(),
price: (*price * 100.0).round() / 100.0,
change: (change * 100.0).round() / 100.0,
timestamp_ms: timestamp_millis(),
}
})
.collect();
Reply::pending(async move {
for update in updates {
println!(
" [PriceFeed] Broadcasting: {} @ ${:.2} ({:+.2})",
update.symbol, update.price, update.change
);
broker.broadcast(update).await;
}
})
});
price_feed.mutate_on::<BroadcastTrade>(|actor, envelope| {
let broker = actor.broker().clone();
actor.model.update_count += 1;
let msg = envelope.message();
let trade = TradeExecuted {
trade_id: format!("TRD{:06}", actor.model.update_count),
symbol: msg.symbol.clone(),
quantity: msg.quantity,
price: msg.price,
side: msg.side.clone(),
};
println!(
" [PriceFeed] Broadcasting trade: {} {} {} @ ${:.2}",
trade.side, trade.quantity, trade.symbol, trade.price
);
Reply::pending(async move {
broker.broadcast(trade).await;
})
});
price_feed.start().await
}
#[acton_main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env().add_directive("acton=info".parse()?))
.init();
println!("====================================================================");
println!(" IPC Subscriptions Example (Server)");
println!("====================================================================");
println!();
let mut runtime = ActonApp::launch_async().await;
let registry = runtime.ipc_registry();
registry.register::<PriceUpdate>("PriceUpdate");
registry.register::<TradeExecuted>("TradeExecuted");
registry.register::<SystemStatus>("SystemStatus");
println!(
"Registered {} IPC message types for subscriptions",
registry.len()
);
println!(" - PriceUpdate: Stock price changes");
println!(" - TradeExecuted: Trade execution events");
println!(" - SystemStatus: System status notifications");
let price_feed = create_price_feed_actor(&mut runtime).await;
println!("\nPrice feed service started");
runtime.ipc_expose("price_feed", price_feed.clone());
println!("Exposed actors: price_feed");
let ipc_config = IpcConfig::load();
let socket_path = ipc_config.socket_path();
let listener_handle = runtime.start_ipc_listener().await?;
println!("IPC listener started");
tokio::time::sleep(Duration::from_millis(50)).await;
if socket_exists(&socket_path) {
println!("Socket ready: {}", socket_path.display());
}
let broker = runtime.broker();
broker
.broadcast(SystemStatus {
status: "market_open".to_string(),
message: "Market is now open for trading".to_string(),
timestamp_ms: timestamp_millis(),
})
.await;
println!();
println!("====================================================================");
println!(" Server is ready for subscription-based IPC!");
println!();
println!(" Subscribable message types:");
println!(" - PriceUpdate : Stock price updates (every 2 seconds)");
println!(" - TradeExecuted : Trade execution events (every 5 seconds)");
println!(" - SystemStatus : System status changes");
println!();
println!(" Run the client in another terminal:");
println!(" cargo run --example ipc_subscriptions_client --features ipc");
println!("====================================================================");
println!();
println!("Press Ctrl+C to shutdown...");
println!();
let price_feed_handle = price_feed.clone();
let price_task = tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(2));
loop {
interval.tick().await;
price_feed_handle.send(BroadcastPrices).await;
}
});
let price_feed_handle2 = price_feed.clone();
let trade_task = tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(5));
let symbols = ["AAPL", "GOOGL", "MSFT", "AMZN"];
let mut idx: u32 = 0;
loop {
interval.tick().await;
let symbol = symbols[(idx as usize) % symbols.len()];
idx += 1;
price_feed_handle2
.send(BroadcastTrade {
symbol: symbol.to_string(),
quantity: idx * 100,
price: f64::from(idx).mul_add(0.5, 150.0),
side: if idx.is_multiple_of(2) { "BUY" } else { "SELL" }.to_string(),
})
.await;
}
});
tokio::signal::ctrl_c().await?;
println!();
println!("Shutting down...");
price_task.abort();
trade_task.abort();
broker
.broadcast(SystemStatus {
status: "market_close".to_string(),
message: "Market is now closed".to_string(),
timestamp_ms: timestamp_millis(),
})
.await;
tokio::time::sleep(Duration::from_millis(100)).await;
listener_handle.stop();
tokio::time::sleep(Duration::from_millis(100)).await;
runtime.shutdown_all().await?;
println!("Server shutdown complete.");
Ok(())
}