ping_echo/
ping_echo.rs

1//! Send a ping/pong, then echo an incrementing counter.
2//!
3//! Start the server with `cargo run --example ping_echo server`, and follow directions to run the client.
4
5use std::time::Duration;
6use tokio::{sync::oneshot, time::sleep};
7
8use sfn_tpn::{Config, NetcodeInterface};
9
10/// Return whether our process is a client.
11///
12/// If not, we must be the server.
13///
14/// Decides based on command line arguments. If no arguments
15/// are supplied, we assume the user wants the process to be
16/// a server.
17fn is_client() -> Result<bool, String> {
18    let mut is_client = false;
19    let mut is_server = false;
20    for arg in std::env::args() {
21        if arg == "client" {
22            is_client = true;
23        }
24        if arg == "server" {
25            is_server = true;
26        }
27    }
28    if is_client && is_server {
29        Err("This process cannot be both the client and the server.".to_string())
30    } else {
31        Ok(is_client)
32    }
33}
34
35/// Gets the first ticket string from the command line arguments.
36fn ticket() -> Result<String, String> {
37    for arg in std::env::args() {
38        if let Some(("--ticket", t)) = arg.split_once("=") {
39            return Ok(t.to_string());
40        }
41    }
42
43    Err("No ticket provided. Clients must provide a ticket to find a server.".to_string())
44}
45
46/// Naively poll `f` with an argument `a: &mut A` until it returns `Ok`.
47async fn wait_for<A, T>(f: fn(&mut A) -> Result<T, ()>, a: &mut A) -> T {
48    loop {
49        if let Ok(t) = f(a) {
50            return t;
51        }
52        sleep(Duration::from_secs(1)).await;
53    }
54}
55
56#[tokio::main]
57async fn main() -> Result<(), String> {
58    if is_client()? {
59        // create a send side & send a ping
60        let mut netcode = NetcodeInterface::new(Config::Ticket(ticket()?));
61        netcode.send_turn(b"ping");
62        println!("Client sent ping");
63
64        assert_eq!(
65            b"pong",
66            &wait_for(NetcodeInterface::try_recv_turn, &mut netcode).await
67        );
68        println!("Client recieved pong");
69
70        let mut counter = 0;
71
72        loop {
73            let bytes = [0, 0, 0, counter];
74            netcode.send_turn(&bytes);
75            println!("Client sent {bytes:?}");
76
77            assert_eq!(
78                &bytes,
79                &wait_for(NetcodeInterface::try_recv_turn, &mut netcode).await
80            );
81            println!("Client got {bytes:?} back");
82
83            counter += 1;
84        }
85    } else {
86        // create the receive side
87        let (send, recv) = oneshot::channel();
88        let mut netcode = NetcodeInterface::new(Config::TicketSender(send));
89
90        println!(
91            "hosting ping_echo. another player may join with \n\n\
92            cargo run --example ping_echo client --ticket={}",
93            recv.await.unwrap()
94        );
95
96        assert_eq!(
97            b"ping",
98            &wait_for(NetcodeInterface::try_recv_turn, &mut netcode).await
99        );
100        println!("Server received ping");
101
102        netcode.send_turn(b"pong");
103        println!("Server sent pong");
104
105        loop {
106            let bytes = wait_for(NetcodeInterface::try_recv_turn, &mut netcode).await;
107            println!("Server received: {:?}", &bytes);
108
109            netcode.send_turn(&bytes);
110            println!("Server echoed");
111
112            sleep(Duration::from_secs(1)).await;
113        }
114    }
115}