use log::{debug, warn};
use mt_pubsub::{Node, Publisher, Qos, Subscriber};
use mt_sea::Sendable;
use mt_service::{ServiceClient, ServiceServer};
use std::sync::Arc;
use tokio::select;
use tokio::sync::Mutex;
use tokio::task::JoinSet;
use tokio::time::{Duration, Instant, sleep_until};
use tokio_util::sync::CancellationToken;
pub mod messages;
use crate::messages::*;
pub mod runner;
use crate::runner::Action;
use crate::runner::ActionRunner;
pub struct ActionServer<T0, T1, T2, T3>
where
T0: Action<T1, T2, T3> + Send + Sync + 'static,
T1: Sendable + Clone,
T2: Sendable + Clone,
T3: Sendable + Clone,
{
pub action: Option<Arc<ActionRunner<T0, T1, T2, T3>>>,
pubber_status: Publisher<ActionStatusMsg>,
pubber_feedback: Publisher<ActionFeedbackMsg<T1>>,
service_send_goal: Arc<ServiceServer<ActionSendGoalRequest<T2>, ActionSendGoalResponse>>,
service_cancel_goal: Arc<ServiceServer<ActionCancelGoalRequest, ActionCancelGoalResponse>>,
service_get_result: Arc<ServiceServer<ActionGetResultRequest, ActionGetResultResponse<T3>>>,
service_internal_server:
Arc<ServiceServer<InternalActionServerStateRequest, InternalActionServerStateResponse>>,
}
impl<T0, T1, T2, T3> ActionServer<T0, T1, T2, T3>
where
T0: Action<T1, T2, T3> + Send + Sync + 'static,
T1: Sendable + Clone,
T2: Sendable + Clone,
T3: Sendable + Clone,
{
pub async fn new(
node: Arc<Node>,
topic: String,
action: T0,
feedback_qos: Qos,
) -> anyhow::Result<Arc<Self>> {
let server = Self {
action: None,
pubber_status: node
.create_publisher(format!("{}/_action/status", &topic), Qos::Reliable)
.await?,
pubber_feedback: node
.create_publisher(format!("{}/_action/feedback", &topic), feedback_qos)
.await?,
service_send_goal: ServiceServer::new(
node.clone(),
format!("{}/_action/send_goal", &topic),
)
.await?,
service_cancel_goal: ServiceServer::new(
node.clone(),
format!("{}/_action/cancel_goal", &topic),
)
.await?,
service_get_result: ServiceServer::new(
node.clone(),
format!("{}/_action/get_result", &topic),
)
.await?,
service_internal_server: ServiceServer::new(
node.clone(),
format!("{}/_action/internal_server", &topic),
)
.await?,
};
let pubber_feedback = server.pubber_feedback.clone();
let server = Arc::new(server);
let action = Some(Arc::new(ActionRunner::new(
Arc::downgrade(&server),
action,
pubber_feedback,
)));
let server_ptr = Arc::as_ptr(&server) as *mut Self;
unsafe {
(*server_ptr).action = action;
}
Ok(server)
}
pub async fn start(this: Arc<Self>, shutdown: CancellationToken) -> anyhow::Result<()> {
let runner = this
.action
.as_ref()
.expect("ActionRunner should be initialized by now")
.clone();
let mut tasks = JoinSet::new();
tasks.spawn(Self::handle_send_goal(
runner.clone(),
this.service_send_goal.clone(),
));
tasks.spawn(Self::handle_cancel_goal(
runner.clone(),
this.service_cancel_goal.clone(),
));
tasks.spawn(Self::handle_get_result(
runner.clone(),
this.service_get_result.clone(),
));
tasks.spawn(Self::handle_internal_server(
runner.clone(),
this.service_internal_server.clone(),
));
tasks.spawn(ActionRunner::start(runner, shutdown.clone()));
loop {
select! {
_ = shutdown.cancelled() => {
warn!("Coordinator unreachable, shutting down action server.");
tasks.abort_all();
break;
}
result = tasks.join_next() => {
if result.is_none() {
break;
}
}
}
}
Ok(())
}
pub async fn publish_status(this: Arc<Self>, msg: ActionStatusMsg) -> anyhow::Result<()> {
let pubber = this.pubber_status.clone();
pubber.publish(&msg).await?;
Ok(())
}
pub async fn publish_feedback(
this: Arc<Self>,
msg: ActionFeedbackMsg<T1>,
) -> anyhow::Result<()> {
let pubber = this.pubber_feedback.clone();
pubber.publish(&msg).await?;
Ok(())
}
async fn handle_send_goal(
action: Arc<ActionRunner<T0, T1, T2, T3>>,
service: Arc<ServiceServer<ActionSendGoalRequest<T2>, ActionSendGoalResponse>>,
) {
ServiceServer::start(
service,
Arc::new(move |msg| {
let action = action.clone();
async move { Ok(action.add_goal(&msg).await) }
}),
)
.await;
}
async fn handle_cancel_goal(
action: Arc<ActionRunner<T0, T1, T2, T3>>,
service: Arc<ServiceServer<ActionCancelGoalRequest, ActionCancelGoalResponse>>,
) {
ServiceServer::start(
service,
Arc::new(move |msg| {
let action = action.clone();
async move { Ok(action.cancel_goal(&msg).await) }
}),
)
.await;
}
async fn handle_get_result(
action: Arc<ActionRunner<T0, T1, T2, T3>>,
service: Arc<ServiceServer<ActionGetResultRequest, ActionGetResultResponse<T3>>>,
) {
ServiceServer::start(
service,
Arc::new(move |msg| {
let action = action.clone();
async move { Ok(action.get_result(&msg).await) }
}),
)
.await;
}
async fn handle_internal_server(
_action: Arc<ActionRunner<T0, T1, T2, T3>>,
service: Arc<
ServiceServer<InternalActionServerStateRequest, InternalActionServerStateResponse>,
>,
) {
ServiceServer::start(
service,
Arc::new(move |_msg| async move { Ok(InternalActionServerStateResponse::Ready) }),
)
.await;
}
}
pub struct ActionClient<T1, T2, T3>
where
T1: Sendable + Clone,
T2: Sendable + Clone,
T3: Sendable + Clone,
{
subber_status: Mutex<Subscriber<ActionStatusMsg>>,
subber_feedback: Mutex<Subscriber<ActionFeedbackMsg<T1>>>,
service_send_goal: ServiceClient<ActionSendGoalRequest<T2>, ActionSendGoalResponse>,
service_cancel_goal: ServiceClient<ActionCancelGoalRequest, ActionCancelGoalResponse>,
service_get_result: ServiceClient<ActionGetResultRequest, ActionGetResultResponse<T3>>,
service_internal_server:
ServiceClient<InternalActionServerStateRequest, InternalActionServerStateResponse>,
}
impl<T1, T2, T3> ActionClient<T1, T2, T3>
where
T1: Sendable + Clone,
T2: Sendable + Clone,
T3: Sendable + Clone,
{
pub async fn new(
node: Arc<Node>,
topic: String,
feedback_qos: Qos,
) -> anyhow::Result<Arc<Self>> {
let subber_queue_size = 10;
Ok(Arc::new(Self {
subber_status: Mutex::new(
node.create_subscriber(
format!("{}/_action/status", &topic),
subber_queue_size,
Qos::Reliable,
)
.await?,
),
subber_feedback: Mutex::new(
node.create_subscriber(
format!("{}/_action/feedback", &topic),
subber_queue_size,
feedback_qos,
)
.await?,
),
service_send_goal: ServiceClient::new(
node.clone(),
format!("{}/_action/send_goal", &topic),
)
.await?,
service_cancel_goal: ServiceClient::new(
node.clone(),
format!("{}/_action/cancel_goal", &topic),
)
.await?,
service_get_result: ServiceClient::new(
node.clone(),
format!("{}/_action/get_result", &topic),
)
.await?,
service_internal_server: ServiceClient::new(
node.clone(),
format!("{}/_action/internal_server", &topic),
)
.await?,
}))
}
pub async fn wait_for_action_server(this: Arc<Self>, timeout: Duration) -> bool {
let timeout = Instant::now() + timeout;
loop {
select! {
_ = sleep_until(timeout) => {
debug!("Timeout reached");
return false;
}
state = this.service_internal_server.request(InternalActionServerStateRequest{}, None) => {
match state {
Ok(InternalActionServerStateResponse::Starting) => {
debug!("Server is starting");
continue;
}
Ok(InternalActionServerStateResponse::Ready) => {
debug!("Server is ready");
return true;
}
Ok(InternalActionServerStateResponse::Broken) => {
debug!("Server is broken");
return false;
}
Err(e) => {
debug!("Server is... I don't even know: {}", e);
return false;
}
}
}
}
}
}
pub async fn get_status(this: Arc<Self>) -> Result<ActionStatusMsg, String> {
let mut subber = this.subber_status.lock().await;
match subber.next().await {
Some(res) => Ok(res),
None => Err("subber_status returned None".to_owned()),
}
}
pub async fn get_feedback(this: Arc<Self>) -> Result<ActionFeedbackMsg<T1>, String> {
let mut subber = this.subber_feedback.lock().await;
match subber.next().await {
Some(res) => Ok(res),
None => Err("subber_feedback returned None".to_owned()),
}
}
pub async fn send_goal(
this: Arc<Self>,
request: ActionSendGoalRequest<T2>,
timeout: Option<Duration>,
) -> Result<ActionSendGoalResponse, String> {
return this.service_send_goal.request(request, timeout).await;
}
pub async fn cancel_goal(
this: Arc<Self>,
request: ActionCancelGoalRequest,
timeout: Option<Duration>,
) -> Result<ActionCancelGoalResponse, String> {
return this.service_cancel_goal.request(request, timeout).await;
}
pub async fn get_result(
this: Arc<Self>,
request: ActionGetResultRequest,
timeout: Option<Duration>,
) -> Result<ActionGetResultResponse<T3>, String> {
return this.service_get_result.request(request, timeout).await;
}
}