use simulacra::{Duration, NodeContext, NodeId, TaskSimBuilder, Time, TopologyBuilder};
const CLIENT: NodeId = NodeId(0);
const FAST_HOP: NodeId = NodeId(1);
const SLOW_HOP: NodeId = NodeId(2);
const SERVER: NodeId = NodeId(3);
const PING_INTERVAL: Duration = Duration::from_millis(50);
const PING_TIMEOUT: Duration = Duration::from_millis(500);
const RUN_UNTIL: Time = Time::from_millis(1300);
#[derive(Debug, Clone)]
enum Msg {
Ping { seq: u32, sent_at: Time },
Pong { seq: u32, sent_at: Time },
}
async fn server(ctx: NodeContext<Msg>) {
let token = ctx.shutdown_token();
while !token.is_cancelled() {
let env = ctx.recv().await;
if let Msg::Ping { seq, sent_at } = env.payload {
ctx.send(env.src, Msg::Pong { seq, sent_at }).await;
}
}
}
async fn client(ctx: NodeContext<Msg>) {
let mut seq: u32 = 0;
let mut fired_fail_link = false;
let mut fired_fail_node = false;
let mut fired_heal_link = false;
while ctx.now() < RUN_UNTIL {
if !fired_fail_link && ctx.now() >= Time::from_millis(200) {
fired_fail_link = true;
println!(
"[{:>10}] FAIL link {}-{} (fast path) — pings should reroute via slow",
ctx.now(),
CLIENT.as_u32(),
FAST_HOP.as_u32()
);
ctx.fail_link(CLIENT, FAST_HOP);
}
if !fired_fail_node && ctx.now() >= Time::from_millis(600) {
fired_fail_node = true;
println!(
"[{:>10}] FAIL node {} (slow hop) — full isolation, pings should time out",
ctx.now(),
SLOW_HOP.as_u32()
);
ctx.fail_node(SLOW_HOP);
}
if !fired_heal_link && ctx.now() >= Time::from_millis(1000) {
fired_heal_link = true;
println!(
"[{:>10}] HEAL link {}-{} — fast path returns",
ctx.now(),
CLIENT.as_u32(),
FAST_HOP.as_u32()
);
ctx.heal_link(CLIENT, FAST_HOP);
}
seq += 1;
let sent_at = ctx.now();
ctx.send(SERVER, Msg::Ping { seq, sent_at }).await;
match ctx.recv_timeout(PING_TIMEOUT).await {
Some(env) => {
if let Msg::Pong { seq, sent_at } = env.payload {
let rtt = env.received_at.saturating_duration_since(sent_at);
println!(
"[{:>10}] ping #{:<3} -> pong rtt={}",
env.received_at, seq, rtt
);
}
}
None => {
println!("[{:>10}] ping #{:<3} -> TIMEOUT (no route)", ctx.now(), seq);
}
}
ctx.sleep(PING_INTERVAL).await;
}
println!("[{:>10}] client: shutting down", ctx.now());
ctx.shutdown_token().cancel();
}
fn main() {
let topology = TopologyBuilder::new(4)
.link(0u32, 1u32, Duration::from_millis(5))
.link(1u32, 3u32, Duration::from_millis(5))
.link(0u32, 2u32, Duration::from_millis(50))
.link(2u32, 3u32, Duration::from_millis(50))
.build();
let sim = TaskSimBuilder::<Msg>::new(topology, 0xF00D).build(|ctx| async move {
match ctx.id() {
CLIENT => client(ctx).await,
SERVER => server(ctx).await,
_ => {}
}
});
let stats = sim.run();
println!("\n--- Simulation Complete ---");
println!("Final time: {}", stats.final_time);
println!("Messages delivered: {}", stats.messages_delivered);
println!("Messages dropped: {}", stats.messages_dropped);
println!("Tasks completed: {}", stats.tasks_completed);
}