use std::{collections::HashMap, fmt::Debug, marker::PhantomData, sync::Arc, time::Duration};
use tokio::sync::{broadcast, RwLock};
use crate::{NTClientReceiver, NTServerSender, NetworkTablesTime, data::{DataType, NetworkTableData}, error::ConnectionClosedError, net::{Announce, BinaryData, ClientboundData, ClientboundTextData, PropertiesData, Publish, ServerboundMessage, ServerboundTextData, SetProperties, Unpublish}, recv_until, topic::Properties};
pub struct Publisher<T: NetworkTableData> {
_phantom: PhantomData<T>,
inner: GenericPublisher,
}
impl<T: NetworkTableData> Debug for Publisher<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Publisher")
.field("id", &self.inner.id)
.field("type", &T::data_type())
.finish()
}
}
impl<T: NetworkTableData> PartialEq for Publisher<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T: NetworkTableData> Eq for Publisher<T> { }
impl<T: NetworkTableData> Publisher<T> {
pub(super) async fn new(
name: String,
properties: Properties,
time: Arc<RwLock<NetworkTablesTime>>,
ws_sender: NTServerSender,
ws_recv: NTClientReceiver,
) -> Result<Self, NewPublisherError> {
Ok(Self {
_phantom: PhantomData,
inner: GenericPublisher::new(name, properties, T::data_type(), time, ws_sender, ws_recv).await?,
})
}
#[cfg(feature = "publish_bypass")]
pub(super) async fn new_bypass(
name: String,
properties: Properties,
time: Arc<RwLock<NetworkTablesTime>>,
ws_sender: NTServerSender,
ws_recv: NTClientReceiver,
) -> Result<Self, ConnectionClosedError> {
Ok(Self {
_phantom: PhantomData,
inner: GenericPublisher::new_bypass(name, properties, T::data_type(), time, ws_sender, ws_recv).await?,
})
}
pub fn unpublish(self) { }
pub fn id(&self) -> i32 {
self.inner.id()
}
pub fn data_type(&self) -> &DataType {
&self.inner.r#type
}
pub async fn set(&self, value: T) -> Result<(), ConnectionClosedError> {
match self.inner.set(value).await {
Ok(()) => Ok(()),
Err(GenericPublishError::ConnectionClosed(_)) => Err(ConnectionClosedError),
Err(GenericPublishError::MismatchedType { .. }) => unreachable!(),
}
}
pub async fn set_default(&self, value: T) -> Result<(), ConnectionClosedError> {
match self.inner.set_default(value).await {
Ok(()) => Ok(()),
Err(GenericPublishError::ConnectionClosed(_)) => Err(ConnectionClosedError),
Err(GenericPublishError::MismatchedType { .. }) => unreachable!(),
}
}
pub async fn update_props(&mut self, new_props: UpdateProps) -> Result<(), broadcast::error::RecvError> {
self.inner.update_props(new_props).await
}
}
pub struct GenericPublisher {
topic: String,
id: i32,
r#type: DataType,
time: Arc<RwLock<NetworkTablesTime>>,
ws_sender: NTServerSender,
ws_recv: NTClientReceiver,
}
impl Debug for GenericPublisher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GenericPublisher")
.field("id", &self.id)
.field("type", &self.r#type)
.finish()
}
}
impl PartialEq for GenericPublisher {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for GenericPublisher { }
impl GenericPublisher {
pub(super) async fn new(
name: String,
properties: Properties,
r#type: DataType,
time: Arc<RwLock<NetworkTablesTime>>,
ws_sender: NTServerSender,
mut ws_recv: NTClientReceiver,
) -> Result<Self, NewPublisherError> {
let id = rand::random();
let pub_message = ServerboundTextData::Publish(Publish { name, pubuid: id, r#type: r#type.clone(), properties });
ws_sender.send(ServerboundMessage::Text(pub_message)).map_err(|_| broadcast::error::RecvError::Closed)?;
let (name, server_type, id) = {
recv_until(&mut ws_recv, |data| {
if let ClientboundData::Text(ClientboundTextData::Announce(Announce { ref name, ref r#type, pubuid: Some(pubuid), .. })) = *data {
Some((name.clone(), r#type.clone(), pubuid))
} else {
None
}
}).await
}?;
if r#type != server_type {
let data = ServerboundTextData::Unpublish(Unpublish { pubuid: id });
let _ = ws_sender.send(ServerboundMessage::Text(data));
return Err(NewPublisherError::MismatchedType { server: server_type, client: r#type });
};
Ok(Self { topic: name, id, r#type, time, ws_sender, ws_recv })
}
#[cfg(feature = "publish_bypass")]
pub(super) async fn new_bypass(
name: String,
properties: Properties,
r#type: DataType,
time: Arc<RwLock<NetworkTablesTime>>,
ws_sender: NTServerSender,
ws_recv: NTClientReceiver,
) -> Result<Self, ConnectionClosedError> {
let id = rand::random();
let pub_message = ServerboundTextData::Publish(Publish { name: name.clone(), pubuid: id, r#type: r#type.clone(), properties });
ws_sender.send(ServerboundMessage::Text(pub_message)).map_err(|_| ConnectionClosedError)?;
tokio::time::sleep(Duration::from_secs_f64(0.1)).await;
Ok(Self { topic: name, id, r#type, time, ws_sender, ws_recv })
}
pub fn unpublish(self) { }
pub fn id(&self) -> i32 {
self.id
}
pub fn data_type(&self) -> &DataType {
&self.r#type
}
pub async fn set<T: NetworkTableData>(&self, value: T) -> Result<(), GenericPublishError> {
let time = self.time.read().await;
self.set_time(value, time.server_time()).await
}
pub async fn set_default<T: NetworkTableData>(&self, value: T) -> Result<(), GenericPublishError> {
self.set_time(value, Duration::ZERO).await
}
pub async fn update_props(&mut self, new_props: UpdateProps) -> Result<(), broadcast::error::RecvError> {
self.ws_sender.send(ServerboundMessage::Text(ServerboundTextData::SetProperties(SetProperties {
name: self.topic.clone(),
update: new_props.into(),
}))).map_err(|_| broadcast::error::RecvError::Closed)?;
recv_until(&mut self.ws_recv, |data| {
if let ClientboundData::Text(ClientboundTextData::Properties(PropertiesData { ref name, .. })) = *data {
if name != &self.topic { return None; };
Some(())
} else {
None
}
}).await?;
Ok(())
}
async fn set_time<T: NetworkTableData>(&self, data: T, timestamp: Duration) -> Result<(), GenericPublishError> {
if self.r#type != T::data_type() {
return Err(GenericPublishError::MismatchedType { server: self.r#type.clone(), client: T::data_type() });
};
let binary = BinaryData::new(self.id, timestamp, data);
self.ws_sender.send(ServerboundMessage::Binary(binary)).map_err(|_| ConnectionClosedError)?;
Ok(())
}
}
impl Drop for GenericPublisher {
fn drop(&mut self) {
let data = ServerboundTextData::Unpublish(Unpublish { pubuid: self.id });
let _ = self.ws_sender.send(ServerboundMessage::Text(data));
}
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum NewPublisherError {
#[error(transparent)]
Recv(#[from] broadcast::error::RecvError),
#[error("mismatched data types! server has {server:?}, but tried to use {client:?} instead")]
MismatchedType {
server: DataType,
client: DataType,
},
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum GenericPublishError {
#[error("mismatched data types! server has {server:?}, but tried to use {client:?} instead")]
MismatchedType {
server: DataType,
client: DataType,
},
#[error(transparent)]
ConnectionClosed(#[from] ConnectionClosedError),
}
macro_rules! builder {
($lit: literal : [
$( #[ $gc_m: meta ] )* fn $get_ckd: ident,
$( #[ $g_m: meta ] )* fn $get: ident,
$( #[ $u_m: meta ] )* fn $update: ident,
$( #[ $s_m: meta ] )* fn $set: ident,
$( #[ $d_m: meta ] )* fn $delete: ident,
$( #[ $k_m: meta ] )* fn $keep: ident,
] : $ty: ty where
$as_pat: pat => $as_value: expr,
$from_pat: pat => $from_value: expr) => {
$( #[ $gc_m ] )*
pub fn $get_ckd(&self) -> Option<PropUpdate<&$ty>> {
match self.get($lit) {
PropUpdate::Set($from_pat) => Some(PropUpdate::Set($from_value)),
PropUpdate::Set(_) => None,
PropUpdate::Delete => Some(PropUpdate::Delete),
PropUpdate::Keep => Some(PropUpdate::Keep),
}
}
$( #[ $g_m ] )*
pub fn $get(&self) -> PropUpdate<&$ty> {
match self.$get_ckd() {
Some(value) => value,
None => panic!("invalid `{}` value", $lit)
}
}
$( #[ $u_m ] )*
pub fn $update(self, value: PropUpdate<$ty>) -> Self {
let value = match value {
PropUpdate::Set($as_pat) => PropUpdate::Set($as_value),
PropUpdate::Delete => PropUpdate::Delete,
PropUpdate::Keep => PropUpdate::Keep,
};
self.update($lit.to_owned(), value)
}
$( #[ $s_m ] )*
pub fn $set(self, value: $ty) -> Self {
let $as_pat = value;
let value = $as_value;
self.set($lit.to_owned(), value)
}
$( #[ $d_m ] )*
pub fn $delete(self) -> Self {
self.delete($lit.to_owned())
}
$( #[ $k_m ] )*
pub fn $keep(self) -> Self {
self.keep($lit.to_owned())
}
};
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct UpdateProps {
inner: HashMap<String, Option<serde_json::Value>>,
}
impl UpdateProps {
pub fn new() -> Self {
Default::default()
}
pub fn with_props_delete(Properties { persistent, retained, cached, extra }: Properties) -> Self {
let mut update = Self::new()
.update_persistent(PropUpdate::from_option_delete(persistent))
.update_retained(PropUpdate::from_option_delete(retained))
.update_cached(PropUpdate::from_option_delete(cached));
for (key, value) in extra {
update = update.set(key, value);
}
update
}
pub fn with_props_keep(Properties { persistent, retained, cached, extra }: Properties) -> Self {
let mut update = Self::new()
.update_persistent(PropUpdate::from_option_keep(persistent))
.update_retained(PropUpdate::from_option_keep(retained))
.update_cached(PropUpdate::from_option_keep(cached));
for (key, value) in extra {
update = update.set(key, value);
}
update
}
pub fn get(&self, key: &str) -> PropUpdate<&serde_json::Value> {
match self.inner.get(key) {
Some(Some(value)) => PropUpdate::Set(value),
Some(None) => PropUpdate::Delete,
None => PropUpdate::Keep,
}
}
pub fn update(mut self, key: String, update: PropUpdate<serde_json::Value>) -> Self {
match update {
PropUpdate::Set(value) => self.inner.insert(key, Some(value)),
PropUpdate::Delete => self.inner.insert(key, None),
PropUpdate::Keep => self.inner.remove(&key),
};
self
}
pub fn set(self, key: String, value: serde_json::Value) -> Self {
self.update(key, PropUpdate::Set(value))
}
pub fn delete(self, key: String) -> Self {
self.update(key, PropUpdate::Delete)
}
pub fn keep(self, key: String) -> Self {
self.update(key, PropUpdate::Keep)
}
builder!("persistent": [
fn persistent_checked,
fn persistent,
fn update_persistent,
fn set_persistent,
fn delete_persistent,
fn keep_persistent,
]: bool where
bool => serde_json::Value::Bool(bool),
serde_json::Value::Bool(value) => value);
builder!("retained": [
fn retained_checked,
fn retained,
fn update_retained,
fn set_retained,
fn delete_retained,
fn keep_retained,
]: bool where
bool => serde_json::Value::Bool(bool),
serde_json::Value::Bool(value) => value);
builder!("cached": [
fn cached_checked,
fn cached,
fn update_cached,
fn set_cached,
fn delete_cached,
fn keep_cached,
]: bool where
bool => serde_json::Value::Bool(bool),
serde_json::Value::Bool(value) => value);
}
impl From<HashMap<String, Option<serde_json::Value>>> for UpdateProps {
fn from(value: HashMap<String, Option<serde_json::Value>>) -> Self {
Self { inner: value }
}
}
impl From<UpdateProps> for HashMap<String, Option<serde_json::Value>> {
fn from(value: UpdateProps) -> Self {
value.inner
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropUpdate<T> {
Set(T),
Delete,
Keep,
}
impl<T> PropUpdate<T> {
pub fn from_option_delete(option: Option<T>) -> Self {
match option {
Some(t) => Self::Set(t),
None => Self::Delete,
}
}
pub fn from_option_keep(option: Option<T>) -> Self {
match option {
Some(t) => Self::Set(t),
None => Self::Keep,
}
}
}