use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::thread;
use std::time::{Duration, Instant};
use subetha_cxc::adaptive_ring::ADAPTIVE_SPSC_PAYLOAD_BYTES;
use subetha_cxc::{
AdaptiveRing, AdaptiveRingSidecar, DefaultRingShapePolicy, RingShape,
};
const ITEMS_PER_STAGE: u64 = 50_000;
const RING_CAPACITY: usize = 1024;
fn main() {
println!("=== AdaptiveRing + sidecar auto-morph E2E ===");
println!("(zero manual morph_to calls; sidecar drives all morphs from peer counts)");
println!();
let ring = Arc::new(
AdaptiveRing::create_anon(4, 4, RING_CAPACITY).expect("ring create"),
);
let policy = DefaultRingShapePolicy {
hysteresis: Duration::from_millis(20),
};
let sidecar = AdaptiveRingSidecar::spawn(
ring.clone(),
policy,
Duration::from_millis(10),
);
let produced = Arc::new(AtomicU64::new(0));
let consumed = Arc::new(AtomicU64::new(0));
let sum_produced = Arc::new(AtomicU64::new(0));
let sum_consumed = Arc::new(AtomicU64::new(0));
let start = Instant::now();
println!("[init] shape={:?}", ring.current_shape());
println!();
println!("[stage 1] register 1 producer + 1 consumer; expect SPSC");
let p0_id = ring.register_producer().expect("p0 register");
let c0_id = ring.register_consumer().expect("c0 register");
let stop_c0 = Arc::new(AtomicBool::new(false));
let cons0 = spawn_consumer(
ring.clone(), c0_id, consumed.clone(),
sum_consumed.clone(), stop_c0.clone(),
);
let prod0_done = spawn_producer(
ring.clone(), p0_id, ITEMS_PER_STAGE,
produced.clone(), sum_produced.clone(),
);
prod0_done.join().expect("p0 done");
wait_until_drained(&consumed, ITEMS_PER_STAGE);
thread::sleep(Duration::from_millis(50));
println!(" shape after 1P/1C activity = {:?}", ring.current_shape());
println!();
println!("[stage 2] register 2nd producer; expect sidecar morph -> MPSC");
let p1_id = ring.register_producer().expect("p1 register");
wait_for_shape(&ring, RingShape::Mpsc, Duration::from_secs(2));
println!(" shape = {:?}", ring.current_shape());
let prod0_again = spawn_producer(
ring.clone(), p0_id, ITEMS_PER_STAGE,
produced.clone(), sum_produced.clone(),
);
let prod1 = spawn_producer(
ring.clone(), p1_id, ITEMS_PER_STAGE,
produced.clone(), sum_produced.clone(),
);
prod0_again.join().expect("p0 again");
prod1.join().expect("p1");
wait_until_drained(&consumed, 3 * ITEMS_PER_STAGE);
println!();
println!("[stage 3] register 2nd consumer; expect sidecar morph -> MPMC");
stop_c0.store(true, Ordering::Release);
cons0.join().expect("c0");
stop_c0.store(false, Ordering::Release);
let c1_id = ring.register_consumer().expect("c1 register");
wait_for_shape(&ring, RingShape::Mpmc, Duration::from_secs(2));
println!(" shape = {:?}", ring.current_shape());
let stop_consumers = Arc::new(AtomicBool::new(false));
let cons0_b = spawn_consumer(
ring.clone(), c0_id, consumed.clone(),
sum_consumed.clone(), stop_consumers.clone(),
);
let cons1 = spawn_consumer(
ring.clone(), c1_id, consumed.clone(),
sum_consumed.clone(), stop_consumers.clone(),
);
let prod0_three = spawn_producer(
ring.clone(), p0_id, ITEMS_PER_STAGE,
produced.clone(), sum_produced.clone(),
);
let prod1_two = spawn_producer(
ring.clone(), p1_id, ITEMS_PER_STAGE,
produced.clone(), sum_produced.clone(),
);
prod0_three.join().expect("p0 three");
prod1_two.join().expect("p1 two");
wait_until_drained(&consumed, 5 * ITEMS_PER_STAGE);
println!();
println!("[stage 4] unregister 1 consumer; expect sidecar morph -> MPSC");
stop_consumers.store(true, Ordering::Release);
cons1.join().expect("c1");
ring.unregister_consumer(c1_id);
wait_for_shape(&ring, RingShape::Mpsc, Duration::from_secs(2));
println!(" shape = {:?}", ring.current_shape());
println!();
println!("[stage 5] unregister 1 producer; expect sidecar morph -> SPSC");
ring.unregister_producer(p1_id);
wait_for_shape(&ring, RingShape::Spsc, Duration::from_secs(2));
println!(" shape = {:?}", ring.current_shape());
stop_consumers.store(false, Ordering::Release);
let final_drain = spawn_consumer(
ring.clone(), c0_id, consumed.clone(),
sum_consumed.clone(), stop_consumers.clone(),
);
let final_push = spawn_producer(
ring.clone(), p0_id, ITEMS_PER_STAGE,
produced.clone(), sum_produced.clone(),
);
final_push.join().expect("final push");
wait_until_drained(&consumed, 6 * ITEMS_PER_STAGE);
stop_consumers.store(true, Ordering::Release);
final_drain.join().expect("final drain");
cons0_b.join().expect("c0 b");
let morphs = sidecar.morphs_triggered();
let elapsed = start.elapsed();
sidecar.shutdown();
let total_produced = produced.load(Ordering::Acquire);
let total_consumed = consumed.load(Ordering::Acquire);
println!();
println!("=== Result ===");
println!(" total elapsed: {elapsed:?}");
println!(" total produced: {total_produced}");
println!(" total consumed: {total_consumed}");
println!(" sidecar morphs issued: {morphs}");
println!(" expected morph sequence: SPSC -> MPSC -> MPMC -> MPSC -> SPSC (4 morphs)");
assert_eq!(
total_produced, total_consumed,
"INTEGRITY FAIL: produced != consumed across morph lifecycle",
);
assert_eq!(
sum_produced.load(Ordering::Acquire),
sum_consumed.load(Ordering::Acquire),
"INTEGRITY FAIL: per-item sums diverge across morph lifecycle",
);
assert!(morphs >= 4,
"sidecar should have triggered at least 4 morphs; got {morphs}");
println!(" integrity: PASS");
println!(" sum_produced == sum_consumed: every item arrived exactly once");
println!(" sidecar drove all morphs from peer-count observations");
}
fn spawn_producer(
ring: Arc<AdaptiveRing>,
producer_id: usize,
items: u64,
produced: Arc<AtomicU64>,
sum_produced: Arc<AtomicU64>,
) -> thread::JoinHandle<()> {
thread::spawn(move || {
let mut buf = [0u8; 56];
for i in 0..items {
buf[..8].copy_from_slice(&i.to_le_bytes());
buf[8] = producer_id as u8;
while ring.try_send(producer_id, &buf).is_err() {
std::hint::spin_loop();
}
produced.fetch_add(1, Ordering::Relaxed);
sum_produced.fetch_add(i, Ordering::Relaxed);
}
})
}
fn spawn_consumer(
ring: Arc<AdaptiveRing>,
consumer_id: usize,
consumed: Arc<AtomicU64>,
sum_consumed: Arc<AtomicU64>,
stop: Arc<AtomicBool>,
) -> thread::JoinHandle<()> {
thread::spawn(move || {
let mut out = [0u8; ADAPTIVE_SPSC_PAYLOAD_BYTES];
while !stop.load(Ordering::Acquire) {
if ring.try_recv(consumer_id, &mut out).is_ok() {
let v = u64::from_le_bytes(out[..8].try_into().unwrap());
consumed.fetch_add(1, Ordering::Relaxed);
sum_consumed.fetch_add(v, Ordering::Relaxed);
} else {
std::hint::spin_loop();
}
}
while ring.try_recv(consumer_id, &mut out).is_ok() {
let v = u64::from_le_bytes(out[..8].try_into().unwrap());
consumed.fetch_add(1, Ordering::Relaxed);
sum_consumed.fetch_add(v, Ordering::Relaxed);
}
})
}
fn wait_until_drained(consumed: &AtomicU64, target: u64) {
while consumed.load(Ordering::Acquire) < target {
thread::sleep(Duration::from_millis(1));
}
}
fn wait_for_shape(ring: &AdaptiveRing, target: RingShape, timeout: Duration) {
let deadline = Instant::now() + timeout;
while ring.current_shape() != target {
if Instant::now() >= deadline {
panic!(
"timed out waiting for shape {:?}; current = {:?}",
target, ring.current_shape(),
);
}
thread::sleep(Duration::from_millis(5));
}
}