use phoxal::participant::ExecutionOrigin;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::time::Duration;
use phoxal::api;
use phoxal::participant::ParticipantLaunch;
use phoxal::prelude::*;
use phoxal::raw::{Bus, BusConfig, run_with_bus};
use serial_test::serial;
static RECEIVED: AtomicU64 = AtomicU64::new(0);
static LAST_LINEAR_BITS: AtomicU32 = AtomicU32::new(0);
const TARGET_LINEAR_MPS: f32 = 0.5;
#[derive(phoxal::Api)]
struct ProducerApi {
target: CommandPublisher<api::drive::Target>,
}
#[phoxal::service(id = "producer", config = (), api = ProducerApi)]
struct Producer;
#[phoxal::behavior]
impl Producer {
#[setup]
async fn setup(ctx: &mut SetupContext<Self>) -> Result<(Self, Self::Api)> {
Ok((
Self,
Self::Api {
target: ctx
.command_publisher(api::topic::new().drive().target())
.await?,
},
))
}
#[step(hz = 50)]
async fn step(&mut self, api: &mut Self::Api, _step: StepContext) -> Result<()> {
api.target.send(api::drive::Target {
linear_x_mps: TARGET_LINEAR_MPS,
angular_z_radps: 0.0,
curvature_limit_radpm: None,
})?;
Ok(())
}
}
#[derive(phoxal::Api)]
struct ConsumerApi {
target: Subscriber<api::drive::Target>,
}
#[phoxal::service(id = "consumer", config = (), api = ConsumerApi)]
struct Consumer;
#[phoxal::behavior]
impl Consumer {
#[setup]
async fn setup(ctx: &mut SetupContext<Self>) -> Result<(Self, Self::Api)> {
let cap = ctx.owner_capability();
Ok((
Self,
Self::Api {
target: ctx
.subscriber(api::topic::internal::new(cap).drive().target(), 32)
.await?,
},
))
}
#[step(hz = 50)]
async fn step(&mut self, api: &mut Self::Api, _step: StepContext) -> Result<()> {
while let Some(received) = api.target.try_recv() {
RECEIVED.fetch_add(1, Ordering::Relaxed);
LAST_LINEAR_BITS.store(received.body.linear_x_mps.to_bits(), Ordering::Relaxed);
}
Ok(())
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
#[serial]
async fn two_runtimes_exchange_a_contract_on_one_bus() {
let bus = Bus::open(BusConfig::in_process("dev", "robot"))
.await
.expect("open shared bus");
let producer = run_with_bus::<Producer, _>(
&bus,
ParticipantLaunch::local("producer-1", "robot")
.with_execution_origin(ExecutionOrigin::mint()),
async { tokio::time::sleep(Duration::from_millis(500)).await },
);
let consumer = run_with_bus::<Consumer, _>(
&bus,
ParticipantLaunch::local("consumer-1", "robot")
.with_execution_origin(ExecutionOrigin::mint()),
async { tokio::time::sleep(Duration::from_millis(500)).await },
);
let (producer_result, consumer_result) = tokio::join!(producer, consumer);
producer_result.expect("producer ran cleanly");
consumer_result.expect("consumer ran cleanly");
bus.close().await.expect("close shared bus");
assert!(
RECEIVED.load(Ordering::Relaxed) > 0,
"the consumer should have received the producer's targets over the shared bus"
);
let last = f32::from_bits(LAST_LINEAR_BITS.load(Ordering::Relaxed));
assert!(
(last - TARGET_LINEAR_MPS).abs() < 1e-6,
"the received value should round-trip the published one (got {last})"
);
}