use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use crate::bus::{
AskQuery, CommandContract, CommandPublisher, ContractBody, DEFAULT_QUERY_TIMEOUT,
DiagnosticContract, DiagnosticPublisher, Latest, MeasurementContract, MeasurementPublisher,
Publish, Querier, StateContract, StatePublisher, Subscribe, Subscriber, TimelineId, Topic,
WorldClockContract,
};
use crate::participant::context::{ResetContext, SetupContext, ShutdownContext, StepContext};
use crate::participant::server::ServerOutcome;
use crate::participant::spec::{IsDriver, IsSimulator, IsTool, StepSchedule};
use phoxal_bus::{Bus, TimelineAuthority, WorldClockPublisher};
#[doc(hidden)]
pub mod __meta {
pub use const_format::concatcp as __concatcp;
#[derive(Clone, Copy)]
pub struct ConstSchema {
bytes: [u8; 65_536],
len: usize,
}
impl Default for ConstSchema {
fn default() -> Self {
Self::new()
}
}
impl ConstSchema {
pub const fn new() -> Self {
Self {
bytes: [0; 65_536],
len: 0,
}
}
pub const fn from_str(value: &str) -> Self {
Self::new().push_str(value)
}
#[must_use]
pub const fn push_str(mut self, value: &str) -> Self {
let value = value.as_bytes();
assert!(
self.len + value.len() <= self.bytes.len(),
"phoxal: const config schema exceeds 64 KiB"
);
let mut index = 0;
while index < value.len() {
self.bytes[self.len + index] = value[index];
index += 1;
}
self.len += value.len();
self
}
pub const fn as_str(&self) -> &str {
let (used, _) = self.bytes.split_at(self.len);
unsafe { core::str::from_utf8_unchecked(used) }
}
}
pub const fn __bytes_of<const N: usize>(s: &str) -> [u8; N] {
let bytes = s.as_bytes();
assert!(
bytes.len() == N,
"phoxal: metadata manifest length mismatch"
);
let mut out = [0u8; N];
let mut i = 0;
while i < N {
out[i] = bytes[i];
i += 1;
}
out
}
}
pub trait ParticipantApi: Send + Sync + Clone + 'static {
#[doc(hidden)]
fn __retain_timeline(&self, timeline: TimelineId);
}
impl ParticipantApi for () {
fn __retain_timeline(&self, _timeline: TimelineId) {}
}
#[doc(hidden)]
pub trait TimelineScopedApiField {
fn __retain_timeline(&self, timeline: TimelineId);
}
impl<B: ContractBody> TimelineScopedApiField for Subscriber<B> {
fn __retain_timeline(&self, timeline: TimelineId) {
Subscriber::__retain_timeline(self, timeline);
}
}
impl<B: ContractBody> TimelineScopedApiField for Latest<B> {
fn __retain_timeline(&self, timeline: TimelineId) {
Latest::__retain_timeline(self, timeline);
}
}
impl<T: TimelineScopedApiField> TimelineScopedApiField for Vec<T> {
fn __retain_timeline(&self, timeline: TimelineId) {
for value in self {
value.__retain_timeline(timeline);
}
}
}
impl<K, T: TimelineScopedApiField> TimelineScopedApiField for std::collections::BTreeMap<K, T> {
fn __retain_timeline(&self, timeline: TimelineId) {
for value in self.values() {
value.__retain_timeline(timeline);
}
}
}
impl<K, T: TimelineScopedApiField, S> TimelineScopedApiField
for std::collections::HashMap<K, T, S>
{
fn __retain_timeline(&self, timeline: TimelineId) {
for value in self.values() {
value.__retain_timeline(timeline);
}
}
}
pub trait DeclaresPublish<B: ?Sized> {}
pub trait DeclaresSubscribe<B: ?Sized> {}
pub trait DeclaresAsk<Req: ?Sized, Resp: ?Sized> {}
pub trait DeclaresServe<Req: ?Sized, Resp: ?Sized> {}
pub trait ParticipantConfig: serde::de::DeserializeOwned + Send + 'static {
#[doc(hidden)]
const __SCHEMA: __meta::ConstSchema;
const SCHEMA_JSON: &'static str = Self::__SCHEMA.as_str();
}
impl ParticipantConfig for () {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::from_str(r#"{"type":"null"}"#);
}
impl<T: ParticipantConfig> ParticipantConfig for Option<T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"anyOf":["#)
.push_str(T::SCHEMA_JSON)
.push_str(r#",{"type":"null"}]}"#);
}
impl<T: ParticipantConfig> ParticipantConfig for Vec<T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"type":"array","items":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
impl<T: ParticipantConfig> ParticipantConfig for std::collections::BTreeMap<String, T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"type":"object","additionalProperties":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
impl<T: ParticipantConfig> ParticipantConfig for std::collections::HashMap<String, T> {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::new()
.push_str(r#"{"type":"object","additionalProperties":"#)
.push_str(T::SCHEMA_JSON)
.push_str("}");
}
macro_rules! primitive_config_schema {
($ty:ty => $schema:literal) => {
impl ParticipantConfig for $ty {
const __SCHEMA: __meta::ConstSchema = __meta::ConstSchema::from_str($schema);
}
};
}
primitive_config_schema!(bool => r#"{"type":"boolean"}"#);
primitive_config_schema!(String => r#"{"type":"string"}"#);
primitive_config_schema!(char => r#"{"type":"string","minLength":1,"maxLength":1}"#);
primitive_config_schema!(i8 => r#"{"type":"integer","format":"int8"}"#);
primitive_config_schema!(i16 => r#"{"type":"integer","format":"int16"}"#);
primitive_config_schema!(i32 => r#"{"type":"integer","format":"int32"}"#);
primitive_config_schema!(i64 => r#"{"type":"integer","format":"int64"}"#);
primitive_config_schema!(i128 => r#"{"type":"integer"}"#);
primitive_config_schema!(isize => r#"{"type":"integer"}"#);
primitive_config_schema!(u8 => r#"{"type":"integer","format":"uint8","minimum":0,"maximum":255}"#);
primitive_config_schema!(u16 => r#"{"type":"integer","format":"uint16","minimum":0,"maximum":65535}"#);
primitive_config_schema!(u32 => r#"{"type":"integer","format":"uint32","minimum":0}"#);
primitive_config_schema!(u64 => r#"{"type":"integer","format":"uint64","minimum":0}"#);
primitive_config_schema!(u128 => r#"{"type":"integer","minimum":0}"#);
primitive_config_schema!(usize => r#"{"type":"integer","minimum":0}"#);
primitive_config_schema!(f32 => r#"{"type":"number","format":"float"}"#);
primitive_config_schema!(f64 => r#"{"type":"number","format":"double"}"#);
pub trait Participant: Sized + Send + 'static {
const KIND: &'static str;
const PARTICIPANT_CLASS: &'static str;
const ID: &'static str;
#[doc(hidden)]
type LaunchPolicy: crate::participant::launch::ParticipantLaunchPolicy;
type Config: ParticipantConfig;
type Api: ParticipantApi;
#[doc(hidden)]
fn __retain_embedded_metadata();
}
#[allow(async_fn_in_trait)]
pub trait ParticipantLifecycle: Participant {
type Snapshot: Send + Sync + 'static;
const HAS_SNAPSHOT: bool;
fn __exclusive_server_topics() -> &'static [&'static str];
fn __snapshot_server_topics() -> &'static [&'static str];
fn __validate_server_topics() -> Result<(), String>;
fn __step_schedule() -> Option<StepSchedule>;
async fn __setup(
ctx: &mut SetupContext<Self>,
config: Self::Config,
) -> crate::Result<(Self, Self::Api)>;
async fn __step(&mut self, api: &mut Self::Api, step: StepContext) -> crate::Result<()>;
async fn __reset(&mut self, api: &mut Self::Api, ctx: ResetContext) -> crate::Result<()>;
async fn __shutdown(&mut self, api: &mut Self::Api, ctx: ShutdownContext) -> crate::Result<()>;
fn __take_snapshot(&self) -> Self::Snapshot;
async fn __serve_exclusive(
&mut self,
api: &mut Self::Api,
topic: &str,
request: &[u8],
) -> ServerOutcome;
fn __serve_snapshot(
snapshot: Arc<Self::Snapshot>,
api: Arc<Self::Api>,
topic: String,
request: Vec<u8>,
) -> Pin<Box<dyn Future<Output = ServerOutcome> + Send>>;
}
pub struct Server<Req, Resp> {
_p: std::marker::PhantomData<fn(Req) -> Resp>,
}
impl<Req, Resp> Server<Req, Resp> {
#[doc(hidden)]
pub fn new() -> Self {
Server {
_p: std::marker::PhantomData,
}
}
}
impl<Req, Resp> Default for Server<Req, Resp> {
fn default() -> Self {
Self::new()
}
}
impl<Req, Resp> Clone for Server<Req, Resp> {
fn clone(&self) -> Self {
*self
}
}
impl<Req, Resp> Copy for Server<Req, Resp> {}
#[allow(async_fn_in_trait)]
pub trait SetupContextApiExt<R: Participant> {
async fn state_publisher<B: StateContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<StatePublisher<B>>
where
R::Api: DeclaresPublish<B>;
async fn measurement_publisher<B: MeasurementContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<MeasurementPublisher<B>>
where
R::Api: DeclaresPublish<B>;
async fn command_publisher<B: CommandContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<CommandPublisher<B>>
where
R::Api: DeclaresPublish<B>;
async fn diagnostic_publisher<B: DiagnosticContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<DiagnosticPublisher<B>>
where
R::Api: DeclaresPublish<B>;
async fn latest<B: ContractBody>(&self, topic: Topic<Subscribe<B>>) -> crate::Result<Latest<B>>
where
R::Api: DeclaresSubscribe<B>;
async fn subscriber<B: ContractBody>(
&self,
topic: Topic<Subscribe<B>>,
depth: usize,
) -> crate::Result<Subscriber<B>>
where
R::Api: DeclaresSubscribe<B>;
async fn querier<Req: ContractBody, Resp: ContractBody>(
&self,
topic: Topic<AskQuery<Req, Resp>>,
) -> crate::Result<Querier<Req, Resp>>
where
R::Api: DeclaresAsk<Req, Resp>;
async fn server<Req: ContractBody, Resp: ContractBody>(
&self,
topic: Topic<AskQuery<Req, Resp>>,
) -> crate::Result<Server<Req, Resp>>
where
R::Api: DeclaresServe<Req, Resp>;
fn robot(&self) -> crate::Result<&crate::model::v0::Robot>;
fn robot_root(&self) -> crate::Result<&std::path::Path>;
}
impl<R: Participant> SetupContextApiExt<R> for SetupContext<R> {
async fn state_publisher<B: StateContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<StatePublisher<B>>
where
R::Api: DeclaresPublish<B>,
{
Ok(StatePublisher::new(self.bus().clone(), &topic)?)
}
async fn measurement_publisher<B: MeasurementContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<MeasurementPublisher<B>>
where
R::Api: DeclaresPublish<B>,
{
Ok(MeasurementPublisher::new(self.bus().clone(), &topic)?)
}
async fn command_publisher<B: CommandContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<CommandPublisher<B>>
where
R::Api: DeclaresPublish<B>,
{
Ok(CommandPublisher::new(self.bus().clone(), &topic)?)
}
async fn diagnostic_publisher<B: DiagnosticContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<DiagnosticPublisher<B>>
where
R::Api: DeclaresPublish<B>,
{
Ok(DiagnosticPublisher::new(self.bus().clone(), &topic)?)
}
async fn latest<B: ContractBody>(&self, topic: Topic<Subscribe<B>>) -> crate::Result<Latest<B>>
where
R::Api: DeclaresSubscribe<B>,
{
Ok(Latest::new(self.bus(), &topic).await?)
}
async fn subscriber<B: ContractBody>(
&self,
topic: Topic<Subscribe<B>>,
depth: usize,
) -> crate::Result<Subscriber<B>>
where
R::Api: DeclaresSubscribe<B>,
{
Ok(Subscriber::new(self.bus(), &topic, depth).await?)
}
async fn querier<Req: ContractBody, Resp: ContractBody>(
&self,
topic: Topic<AskQuery<Req, Resp>>,
) -> crate::Result<Querier<Req, Resp>>
where
R::Api: DeclaresAsk<Req, Resp>,
{
Ok(Querier::new(
self.bus().clone(),
&topic,
DEFAULT_QUERY_TIMEOUT,
)?)
}
async fn server<Req: ContractBody, Resp: ContractBody>(
&self,
topic: Topic<AskQuery<Req, Resp>>,
) -> crate::Result<Server<Req, Resp>>
where
R::Api: DeclaresServe<Req, Resp>,
{
let _ = topic;
Ok(Server::new())
}
fn robot(&self) -> crate::Result<&crate::model::v0::Robot> {
self.robot_ref().ok_or_else(|| {
anyhow::anyhow!(
"no robot model is bound (this participant was launched without a robot root)"
)
})
}
fn robot_root(&self) -> crate::Result<&std::path::Path> {
self.robot_root_ref().ok_or_else(|| {
anyhow::anyhow!(
"no robot root is bound (this participant was launched without a robot root)"
)
})
}
}
pub trait SetupContextDriverExt {
fn component(&self) -> crate::Result<&str>;
}
impl<R: Participant + IsDriver> SetupContextDriverExt for SetupContext<R> {
fn component(&self) -> crate::Result<&str> {
self.component_instance().ok_or_else(|| {
anyhow::anyhow!("no component instance is bound (this driver was launched without one)")
})
}
}
#[allow(async_fn_in_trait)]
pub trait SetupContextSimulatorExt<R: Participant> {
fn component(&self) -> crate::Result<&str>;
fn timeline_authority(&self, timeline: TimelineId) -> crate::Result<TimelineAuthority>;
async fn world_clock_publisher<B: WorldClockContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<WorldClockPublisher<B>>
where
R::Api: DeclaresPublish<B>;
}
impl<R: Participant + IsSimulator> SetupContextSimulatorExt<R> for SetupContext<R> {
fn component(&self) -> crate::Result<&str> {
self.component_instance().ok_or_else(|| {
anyhow::anyhow!(
"no component instance is bound (this simulator was launched without one)"
)
})
}
fn timeline_authority(&self, timeline: TimelineId) -> crate::Result<TimelineAuthority> {
Ok(TimelineAuthority::__mint(timeline)?)
}
async fn world_clock_publisher<B: WorldClockContract>(
&self,
topic: Topic<Publish<B>>,
) -> crate::Result<WorldClockPublisher<B>>
where
R::Api: DeclaresPublish<B>,
{
Ok(WorldClockPublisher::__mint(self.bus().clone(), &topic)?)
}
}
pub trait SetupContextToolExt {
fn raw_bus(&self) -> Bus;
fn execution(&self) -> crate::bus::ExecutionId;
}
impl<R: Participant + IsTool> SetupContextToolExt for SetupContext<R> {
fn raw_bus(&self) -> Bus {
self.bus().clone()
}
fn execution(&self) -> crate::bus::ExecutionId {
self.bus().execution()
}
}