swarmbot_interfaces/
lib.rs1use futures::{SinkExt, StreamExt};
2use serde::{Deserialize, Serialize};
3use tokio::net::{TcpListener, TcpStream, ToSocketAddrs};
4use tungstenite::Message;
5
6use crate::types::{BlockLocation, Selection2D};
7
8pub mod types;
9
10type Id = u64;
11
12#[derive(Serialize, Deserialize, Debug)]
16pub struct Mine {
17 pub sel: Selection2D,
18}
19
20#[derive(Serialize, Deserialize, Debug)]
22pub struct GoTo {
23 pub location: BlockLocation,
24}
25
26#[derive(Serialize, Deserialize, Debug)]
28pub struct Attack {
29 pub name: String,
30}
31
32#[derive(Serialize, Deserialize, Debug)]
33pub struct Cancelled {
34 pub id: Id,
35}
36
37#[derive(Serialize, Deserialize, Debug)]
38pub struct Finished {
39 pub id: Id,
40}
41
42macro_rules! commands {
43 (
44 $($command: ident),*
45 ) =>
46 {
47 #[derive(Serialize, Deserialize, Debug)]
48 #[serde(rename_all = "lowercase")]
49 #[serde(tag = "path")]
50 pub enum CommandData {
51 $($command($command)),*
52 }
53
54 };
55}
56
57commands! {
58 Mine, GoTo, Attack, Cancelled, Finished
59}
60
61#[derive(Serialize, Deserialize, Debug)]
62pub struct Command {
63 id: u64,
64 data: CommandData,
65}
66
67pub struct Comm {
68 rx: tokio::sync::mpsc::UnboundedReceiver<Command>,
69 tx: tokio::sync::mpsc::UnboundedSender<Command>,
70}
71
72type Res<T = ()> = Result<T, Box<dyn std::error::Error>>;
73
74fn incoming(msg: tungstenite::Message) -> Res<Command> {
75 let data = msg.into_data();
76 let command: Command = serde_json::from_slice(&data)?;
77 Ok(command)
78}
79
80fn outgoing(command: Command) -> Res<Message> {
81 let string = serde_json::to_string(&command)?;
82 Ok(Message::Text(string))
83}
84
85impl Comm {
86 pub async fn recv(&mut self) -> Command {
87 self.rx.recv().await.unwrap()
88 }
89
90 pub fn send(&mut self, command: Command) {
91 self.tx.send(command).unwrap();
92 }
93
94 pub async fn connect<A: ToSocketAddrs>(addr: A) -> Res<Self> {
95 let stream = TcpStream::connect(addr).await?;
96 let (recv_tx, recv_rx) = tokio::sync::mpsc::unbounded_channel();
97 let (send_tx, mut send_rx) = tokio::sync::mpsc::unbounded_channel();
98 let mut ws = tokio_tungstenite::accept_async(stream).await?;
99 tokio::spawn(async move {
100 let mut msg_to_send = None;
101 tokio::select! {
102 val = ws.next() => {
103 if let Some(Ok(msg)) = val {
104 if let Ok(cmd) = incoming(msg) {
105 let _ = recv_tx.send(cmd);
106 }
107 }
108 }
109 val = send_rx.recv() => {
110 if let Some(cmd) = val {
111 if let Ok(msg) = outgoing(cmd) {
112 msg_to_send = Some(msg);
113 }
114 }
115 }
116 }
117
118 if let Some(msg) = msg_to_send {
119 let _ = ws.send(msg).await;
120 }
121 });
122
123 Ok(Self {
124 rx: recv_rx,
125 tx: send_tx,
126 })
127 }
128
129 pub async fn host<A: ToSocketAddrs>(addr: A) -> Res<Self> {
130 let server = TcpListener::bind(addr).await?;
131 let (recv_tx, recv_rx) = tokio::sync::mpsc::unbounded_channel();
132 let (send_tx, mut send_rx) = tokio::sync::mpsc::unbounded_channel();
133
134 tokio::spawn(async move {
135 loop {
136 let _ignored: Res = async {
137 let (stream, _) = server.accept().await?;
138 let mut ws = tokio_tungstenite::accept_async(stream).await?;
139
140 let mut msg_to_send = None;
141 tokio::select! {
142 val = ws.next() => {
143 if let Some(Ok(msg)) = val {
144 if let Ok(cmd) = incoming(msg) {
145 let _ = recv_tx.send(cmd);
146 }
147 }
148 }
149 val = send_rx.recv() => {
150 if let Some(cmd) = val {
151 if let Ok(msg) = outgoing(cmd) {
152 msg_to_send = Some(msg)
153 }
154 }
155 }
156 }
157
158 if let Some(msg) = msg_to_send {
159 let _ = ws.send(msg).await;
160 }
161
162 Ok(())
163 }
164 .await;
165 }
166 });
167
168 Ok(Self {
169 rx: recv_rx,
170 tx: send_tx,
171 })
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use crate::{Attack, Command, CommandData};
178
179 #[test]
180 fn test() {
181 let command = Command {
182 id: 123,
183 data: CommandData::Attack(Attack {
184 name: "hello".to_string(),
185 }),
186 };
187
188 serde_json::to_string(&command).unwrap();
189 }
190}