Measure multiplayer relay ping
This commit is contained in:
1 parent
4834adca7b
commit
ef148087f4
2 files changed
+81
-1
No files matched your search
+15
-1
@@ -19,7 +19,7 @@
|
|||||||
// {"t":"set_ai","side":2,"ai":true} -> the host marks an empty seat as AI, or clears it again
|
// {"t":"set_ai","side":2,"ai":true} -> the host marks an empty seat as AI, or clears it again
|
||||||
// {"t":"leave"} -> a player leaves; a started human seat becomes AI-controlled while anyone is connected
|
// {"t":"leave"} -> a player leaves; a started human seat becomes AI-controlled while anyone is connected
|
||||||
//
|
//
|
||||||
// Server messages include: hello, rooms, room, start, turn, sum, chat, closed, error.
|
// Server messages include: hello, rooms, room, start, turn, sum, chat, closed, error, pong.
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -276,6 +276,9 @@ impl Netplay {
|
|||||||
self.leave_room(conn, &code, false);
|
self.leave_room(conn, &code, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"ping" => {
|
||||||
|
self.send(conn, json!({"t":"pong","c":msg.get("c").cloned().unwrap_or(Value::Null)}));
|
||||||
|
}
|
||||||
_ => self.error(conn, "unknown message"),
|
_ => self.error(conn, "unknown message"),
|
||||||
}
|
}
|
||||||
Vec::new()
|
Vec::new()
|
||||||
@@ -988,4 +991,15 @@ mod tests {
|
|||||||
assert_eq!(team_red["side"].as_u64(), Some(0));
|
assert_eq!(team_red["side"].as_u64(), Some(0));
|
||||||
assert!(rx2.try_recv().is_err());
|
assert!(rx2.try_recv().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_relay_answers_a_ping_with_the_same_correlation_number() {
|
||||||
|
let mut net = Netplay::new();
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||||
|
let red = net.connect(tx, Some("red".into()), None);
|
||||||
|
net.handle(red, json!({"t":"ping","c":7}));
|
||||||
|
|
||||||
|
let pong = until(&mut rx, "pong");
|
||||||
|
assert_eq!(pong["c"].as_u64(), Some(7));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -118,6 +118,10 @@ pub struct MatchClient {
|
|||||||
start: Option<StartInfo>,
|
start: Option<StartInfo>,
|
||||||
turns: Vec<(u32, Vec<Command>, Vec<u8>)>,
|
turns: Vec<(u32, Vec<Command>, Vec<u8>)>,
|
||||||
checks: Vec<(Option<u8>, u32, u64)>,
|
checks: Vec<(Option<u8>, u32, u64)>,
|
||||||
|
pub ping_ms: f32,
|
||||||
|
ping_seq: u64,
|
||||||
|
ping_next: f64,
|
||||||
|
ping_pending: Option<(u64, f64)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MatchClient {
|
impl MatchClient {
|
||||||
@@ -137,6 +141,10 @@ impl MatchClient {
|
|||||||
start: None,
|
start: None,
|
||||||
turns: Vec::new(),
|
turns: Vec::new(),
|
||||||
checks: Vec::new(),
|
checks: Vec::new(),
|
||||||
|
ping_ms: 0.0,
|
||||||
|
ping_seq: 0,
|
||||||
|
ping_next: 0.0,
|
||||||
|
ping_pending: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +164,9 @@ impl MatchClient {
|
|||||||
self.connected = true;
|
self.connected = true;
|
||||||
self.name = name.to_string();
|
self.name = name.to_string();
|
||||||
self.status = None;
|
self.status = None;
|
||||||
|
self.ping_ms = 0.0;
|
||||||
|
self.ping_pending = None;
|
||||||
|
self.ping_next = 0.0;
|
||||||
self.rooms_request();
|
self.rooms_request();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +178,25 @@ impl MatchClient {
|
|||||||
self.start = None;
|
self.start = None;
|
||||||
self.turns.clear();
|
self.turns.clear();
|
||||||
self.status = Some("offline".into());
|
self.status = Some("offline".into());
|
||||||
|
self.ping_ms = 0.0;
|
||||||
|
self.ping_pending = None;
|
||||||
|
self.ping_next = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tick_ping(&mut self, now: f64) {
|
||||||
|
if let Some((_, start)) = self.ping_pending {
|
||||||
|
if now - start > 5.0 {
|
||||||
|
self.ping_pending = None;
|
||||||
|
self.ping_ms = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.connected || self.ping_pending.is_some() || now < self.ping_next {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.ping_seq = self.ping_seq.wrapping_add(1);
|
||||||
|
self.outbox.push_back(json!({"t":"ping","c":self.ping_seq}).to_string());
|
||||||
|
self.ping_pending = Some((self.ping_seq, now));
|
||||||
|
self.ping_next = now + 2.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rooms_request(&mut self) {
|
pub fn rooms_request(&mut self) {
|
||||||
@@ -281,6 +311,10 @@ impl MatchClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn feed(&mut self, text: &str) {
|
pub fn feed(&mut self, text: &str) {
|
||||||
|
self.feed_at(text, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn feed_at(&mut self, text: &str, now: f64) {
|
||||||
let v: Value = match serde_json::from_str(text) {
|
let v: Value = match serde_json::from_str(text) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
@@ -291,6 +325,16 @@ impl MatchClient {
|
|||||||
self.name = n.to_string();
|
self.name = n.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"pong" => {
|
||||||
|
if let Some(c) = v.get("c").and_then(|x| x.as_u64()) {
|
||||||
|
if let Some((seq, start)) = self.ping_pending {
|
||||||
|
if seq == c {
|
||||||
|
self.ping_ms = ((now - start).max(0.0) * 1000.0) as f32;
|
||||||
|
self.ping_pending = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
"rooms" => self.parse_rooms(&v),
|
"rooms" => self.parse_rooms(&v),
|
||||||
"room" => self.parse_room(&v),
|
"room" => self.parse_room(&v),
|
||||||
"start" => self.parse_start(&v),
|
"start" => self.parse_start(&v),
|
||||||
@@ -563,6 +607,28 @@ mod tests {
|
|||||||
assert_eq!(chat[0], (Some(0), "red".to_string(), "hello".to_string()));
|
assert_eq!(chat[0], (Some(0), "red".to_string(), "hello".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn the_bridge_times_the_server_ping() {
|
||||||
|
let mut client = MatchClient::new();
|
||||||
|
client.on_connect("red");
|
||||||
|
pop(&mut client); // rooms
|
||||||
|
|
||||||
|
client.tick_ping(10.0);
|
||||||
|
let req = pop(&mut client);
|
||||||
|
assert_eq!(req["t"].as_str(), Some("ping"));
|
||||||
|
assert_eq!(req["c"].as_u64(), Some(1));
|
||||||
|
|
||||||
|
client.feed_at(r#"{"t":"pong","c":1}"#, 10.25);
|
||||||
|
assert!(client.ping_ms > 200.0 && client.ping_ms < 300.0, "the pong came 250 ms after the ping");
|
||||||
|
|
||||||
|
client.tick_ping(11.0);
|
||||||
|
assert!(client.outbox.is_empty(), "the next ping waits for its two-second interval");
|
||||||
|
|
||||||
|
client.tick_ping(12.0);
|
||||||
|
let next = pop(&mut client);
|
||||||
|
assert_eq!(next["c"].as_u64(), Some(2));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn the_bridge_holds_ai_seats_through_the_lobby() {
|
fn the_bridge_holds_ai_seats_through_the_lobby() {
|
||||||
let mut client = MatchClient::new();
|
let mut client = MatchClient::new();
|
||||||
|
|||||||
Reference in new issue
Block a user