swarmbot_interfaces/
lib.rs

1use 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/// The mine command.
13/// Mine the given selection.
14/// A global command. The process should allocate appropriately to children.
15#[derive(Serialize, Deserialize, Debug)]
16pub struct Mine {
17    pub sel: Selection2D,
18}
19
20/// A navigation command to go to the given block location
21#[derive(Serialize, Deserialize, Debug)]
22pub struct GoTo {
23    pub location: BlockLocation,
24}
25
26/// Attack a given player
27#[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}