use std::time::Duration;
use acton_reactive::ipc::{socket_exists, IpcConfig};
use acton_reactive::prelude::*;
use tracing_subscriber::EnvFilter;
#[acton_message(ipc)]
struct CountdownRequest {
start: u32,
delay_ms: u64,
}
#[acton_message(ipc)]
struct CountdownTick {
number: u32,
is_final: bool,
}
#[acton_message(ipc)]
struct ListItemsRequest {
page_size: usize,
}
#[acton_message(ipc)]
struct ItemPage {
page: usize,
items: Vec<String>,
has_more: bool,
}
#[acton_actor]
struct CountdownState {
countdowns_started: usize,
}
#[acton_actor]
struct ListServiceState {
#[allow(dead_code)]
request_count: usize,
}
const SAMPLE_ITEMS: &[&str] = &[
"Apple",
"Banana",
"Cherry",
"Date",
"Elderberry",
"Fig",
"Grape",
"Honeydew",
"Kiwi",
"Lemon",
];
async fn create_countdown_actor(runtime: &mut ActorRuntime) -> ActorHandle {
let mut countdown = runtime.new_actor_with_name::<CountdownState>("countdown".to_string());
countdown.mutate_on::<CountdownRequest>(|actor, envelope| {
let msg = envelope.message();
let start = msg.start;
let delay_ms = msg.delay_ms;
actor.model.countdowns_started += 1;
println!(
" [Countdown] Starting countdown from {} with {}ms delay (#{})...",
start, delay_ms, actor.model.countdowns_started
);
let reply_envelope = envelope.reply_envelope();
Reply::pending(async move {
for i in (0..=start).rev() {
let tick = CountdownTick {
number: i,
is_final: i == 0,
};
println!(" [Countdown] Sending tick: {i}");
reply_envelope.send(tick).await;
if i > 0 {
tokio::time::sleep(Duration::from_millis(delay_ms)).await;
}
}
println!(" [Countdown] Stream complete");
})
});
countdown.start().await
}
async fn create_list_actor(runtime: &mut ActorRuntime) -> ActorHandle {
let mut list_service =
runtime.new_actor_with_name::<ListServiceState>("list_service".to_string());
list_service.act_on::<ListItemsRequest>(|_actor, envelope| {
let msg = envelope.message();
let page_size = msg.page_size.max(1);
let items: Vec<String> = SAMPLE_ITEMS.iter().map(|s| (*s).to_string()).collect();
println!(
" [ListService] Streaming {} items in pages of {}...",
items.len(),
page_size
);
let reply_envelope = envelope.reply_envelope();
Reply::pending(async move {
let chunks: Vec<_> = items.chunks(page_size).collect();
let total_pages = chunks.len();
for (idx, chunk) in chunks.iter().enumerate() {
let page_num = idx + 1;
let page = ItemPage {
page: page_num,
items: chunk.to_vec(),
has_more: page_num < total_pages,
};
println!(
" [ListService] Sending page {}/{} with {} items",
page_num,
total_pages,
page.items.len()
);
reply_envelope.send(page).await;
if page_num < total_pages {
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
println!(" [ListService] Stream complete");
})
});
list_service.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 Streaming Response Example (Server) ║");
println!("╚══════════════════════════════════════════════════════════════╝");
println!();
let mut runtime = ActonApp::launch_async().await;
let registry = runtime.ipc_registry();
registry.register::<CountdownRequest>("CountdownRequest");
registry.register::<CountdownTick>("CountdownTick");
registry.register::<ListItemsRequest>("ListItemsRequest");
registry.register::<ItemPage>("ItemPage");
println!("📝 Registered {} IPC message types", registry.len());
let countdown = create_countdown_actor(&mut runtime).await;
println!("⏱️ Countdown service started");
let list_service = create_list_actor(&mut runtime).await;
println!("📋 List service started");
runtime.ipc_expose("countdown", countdown.clone());
runtime.ipc_expose("list_service", list_service.clone());
println!("🔗 Exposed actors: countdown, list_service");
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());
}
println!();
println!("════════════════════════════════════════════════════════════════");
println!(" Server is ready for streaming IPC communication!");
println!(" Run the client example in another terminal:");
println!(" cargo run --example ipc_streaming_client --features ipc");
println!("════════════════════════════════════════════════════════════════");
println!();
println!("Press Ctrl+C to shutdown...");
println!();
tokio::signal::ctrl_c().await?;
println!();
println!("Shutting down...");
listener_handle.stop();
tokio::time::sleep(Duration::from_millis(100)).await;
runtime.shutdown_all().await?;
println!("Server shutdown complete.");
Ok(())
}