Maelstrom: Fixed problem where network timeout wasn't properly being hit under packet loss conditions.

https://github.com/libsdl-org/Maelstrom/commit/e8d009625c8ac8d58242edb336a67d9dfb397b76

From e8d009625c8ac8d58242edb336a67d9dfb397b76 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sat, 26 Nov 2011 22:07:16 -0500
Subject: [PATCH] Fixed problem where network timeout wasn't properly being hit
 under packet loss conditions.

---
 game/netplay.cpp | 139 ++++++++++++++++++++++++++++++-----------------
 1 file changed, 89 insertions(+), 50 deletions(-)

diff --git a/game/netplay.cpp b/game/netplay.cpp
index 5f08150c..16a52ffc 100644
--- a/game/netplay.cpp
+++ b/game/netplay.cpp
@@ -37,6 +37,9 @@
 // Define this to simulate packet loss
 //#define DEBUG_PACKETLOSS 5
 
+// How long we wait for an ack
+#define NETWORK_TIMEOUT	2*FRAME_DELAY_MS
+
 
 UDPsocket gNetFD;
 
@@ -55,7 +58,10 @@ static Uint32 WaitingAcks[MAX_NODES];
 
 /* We cache one packet if the other player is ahead of us */
 static DynamicPacket Packet;
-static DynamicPacket CachedPacket[MAX_NODES];
+static struct {
+	Uint32 frame;
+	DynamicPacket packet;
+} CachedPacket[MAX_NODES];
 
 /* When we're done we have our input for the frame */
 static DynamicPacket QueuedInput;
@@ -96,14 +102,15 @@ int InitNetData(bool hosting)
 #endif
 
 	/* Initialize network game variables */
-	NextFrame = 0;
+	NextFrame = 1;
 	AdvancedFrame = true;
 	OutBound[0].Reset();
 	OutBound[1].Reset();
 	CurrOut = 0;
 	SDL_zero(WaitingAcks);
 	for (i = 0; i < MAX_NODES; ++i) {
-		CachedPacket[i].Reset();
+		CachedPacket[i].frame = 0;
+		CachedPacket[i].packet.Reset();
 	}
 	QueuedInput.Reset();
 	FrameInput.Reset();
@@ -148,7 +155,7 @@ int CheckPlayers(void)
 	}
 
 	/* Bind all of our network nodes to the broadcast channel */
-	for (i = 0; i < MAX_NODES; ++i) {
+	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
 		if (gGameInfo.IsNetworkNode(i)) {
 			const GameInfoNode *node = gGameInfo.GetNode(i);
 			SDLNet_UDP_Bind(gNetFD, 0, &node->address);
@@ -164,9 +171,20 @@ void QueueInput(Uint8 value)
 	QueuedInput.Write(value);
 }
 
+static Uint32 NodeTimeout(Uint32 now)
+{
+	Uint32 timeout;
+
+	timeout = now + NETWORK_TIMEOUT;
+	if (!timeout) {
+		timeout = 1;
+	}
+	return timeout;
+}
+
 static bool WaitingForAck()
 {
-	for (int i = 0; i < MAX_NODES; ++i) {
+	for (int i = 0; i < gGameInfo.GetNumNodes(); ++i) {
 		if (WaitingAcks[i]) {
 			return true;
 		}
@@ -174,6 +192,25 @@ static bool WaitingForAck()
 	return false;
 }
 
+static bool HasTimedOut(int index, Uint32 now)
+{
+	if (!WaitingAcks[index]) {
+		return false;
+	}
+	return int(WaitingAcks[index]-now) < 0;
+}
+
+static Uint32 NextTimeout(Uint32 now)
+{
+	Uint32 timeout = NETWORK_TIMEOUT;
+	for (int i = 0; i < gGameInfo.GetNumNodes(); ++i) {
+		if (WaitingAcks[i]) {
+			timeout = SDL_min((now - WaitingAcks[i]), timeout);
+		}
+	}
+	return timeout;
+}
+
 static bool ProcessSync(int index, DynamicPacket &packet)
 {
 	Uint32 seed;
@@ -202,53 +239,50 @@ static bool ProcessSync(int index, DynamicPacket &packet)
 static SYNC_RESULT AwaitSync()
 {
 	int i;
-	int timeout;
 	Uint32 frame;
 
 	// Send the packet to anyone waiting
-	for (i = 0; i < MAX_NODES; ++i) {
+	Uint32 now = SDL_GetTicks();
+	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
 		if (WaitingAcks[i]) {
+#if DEBUG_NETWORK >= 2
+error("Sending packet for current frame (%ld)\r\n", NextFrame);
+#endif
 			SDLNet_UDP_Send(gNetFD, i+1, &CurrPacket);
+			WaitingAcks[i] = NodeTimeout(now);
 		}
 	}
 
 	// See if we have cached network packets
-	for (i = 0; i < MAX_NODES; ++i) {
-		if (CachedPacket[i].len > 0 && WaitingAcks[i]) {
-			if (!ProcessSync(i, CachedPacket[i])) {
+	// Note that we always send the packet for the current frame first
+	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
+		if (CachedPacket[i].frame == NextFrame) {
+			if (!ProcessSync(i, CachedPacket[i].packet)) {
 				return SYNC_CORRUPT;
 			}
 			WaitingAcks[i] = 0;
+			CachedPacket[i].frame = 0;
 		}
-		CachedPacket[i].Reset();
 	}
 
 	/* Wait for Ack's */
-	timeout = 0;
 	while (WaitingForAck()) {
-		int ready = SDLNet_CheckSockets(SocketSet, 2*FRAME_DELAY_MS);
-		if (ready < 0) {
-			error("Network error: SDLNet_CheckSockets()\r\n");
-			return SYNC_NETERROR;
-		}
-		if (ready == 0) {
+		now = SDL_GetTicks();
+		for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
+			if (HasTimedOut(i, now)) {
 #if DEBUG_NETWORK >= 1
 error("Timed out waiting for frame %ld\r\n", NextFrame);
 #endif
-			/* Timeout, resend the sync packet */
-			for (i = 0; i < MAX_NODES; ++i) {
-				if (WaitingAcks[i]) {
-					SDLNet_UDP_Send(gNetFD, i+1, &CurrPacket);
-				}
-			}
-
-			/* Don't wait forever */
-			++timeout;
-			if ( timeout == (PING_TIMEOUT/100) ) {
 				return SYNC_TIMEOUT;
 			}
 		}
-		if ( ready <= 0 ) {
+
+		int ready = SDLNet_CheckSockets(SocketSet, NextTimeout(now));
+		if (ready < 0) {
+			error("Network error: SDLNet_CheckSockets()\r\n");
+			return SYNC_NETERROR;
+		}
+		if (ready == 0) {
 			continue;
 		}
 
@@ -305,7 +339,7 @@ error("NEW_GAME_ACK packet\r\n");
 		}
 		int index = gGameInfo.GetNodeIndex(nodeID);
 		if (index < 0) {
-			error("Warning: packet from unknown source\r\n");
+			/* This must be from an old node */
 			continue;
 		}
 
@@ -315,13 +349,13 @@ error("NEW_GAME_ACK packet\r\n");
 			continue;
 		}
 #if DEBUG_NETWORK >= 2
-error("Received a packet of frame %lu from player %d\r\n", frame, index+1);
+error("Received a packet of frame %lu from node %d\r\n", frame, index);
 #endif
 		if (frame == NextFrame) {
 			/* Ignore it if it is a duplicate packet */
 			if (!WaitingAcks[index]) {
 #if DEBUG_NETWORK >= 1
-error("Ignoring duplicate packet for frame %lu from player %d\r\n", frame, index+1);
+error("Ignoring duplicate packet for frame %lu from node %d\r\n", frame, index);
 #endif
 				continue;
 			}
@@ -344,9 +378,10 @@ error("Received packet for next frame! (%lu, current = %lu)\r\n",
 					frame, NextFrame);
 #endif
 			/* Cache this frame for next round */
-			CachedPacket[index].Reset();
-			CachedPacket[index].Write(Packet);
-			CachedPacket[index].Seek(0);
+			CachedPacket[index].frame = frame;
+			CachedPacket[index].packet.Reset();
+			CachedPacket[index].packet.Write(Packet);
+			CachedPacket[index].packet.Seek(0);
 		}
 #if DEBUG_NETWORK >= 1
 else
@@ -393,9 +428,10 @@ SYNC_RESULT SyncNetwork(void)
 	FrameInput.Write(QueuedInput);
 
 	// See if we need to do network synchronization
+	Uint32 now = SDL_GetTicks();
 	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
 		if (gGameInfo.IsNetworkNode(i)) {
-			WaitingAcks[i] = gGameInfo.GetNode(i)->nodeID;
+			WaitingAcks[i] = NodeTimeout(now);
 		} else {
 			WaitingAcks[i] = 0;
 		}
@@ -446,9 +482,10 @@ int Send_NewGame()
 	SDLNet_UDP_Send(gNetFD, 0, &newgame);
 
 	/* Get ready for responses */
+	Uint32 now = SDL_GetTicks();
 	for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
 		if (gGameInfo.IsNetworkNode(i)) {
-			WaitingAcks[i] = gGameInfo.GetNode(i)->nodeID;
+			WaitingAcks[i] = NodeTimeout(now);
 		} else {
 			WaitingAcks[i] = 0;
 		}
@@ -460,8 +497,8 @@ int Send_NewGame()
 		strcpy(message, "Waiting for players:");
 		for (i = 0; i < MAX_PLAYERS; ++i) {
 			const GameInfoPlayer *player = gGameInfo.GetPlayer(i);
-			for (j = 0; j < MAX_NODES; ++j) {
-				if (player->nodeID == WaitingAcks[j]) {
+			for (j = 0; j < gGameInfo.GetNumNodes(); ++j) {
+				if (player->nodeID == gGameInfo.GetNode(j)->nodeID) {
 					sprintf(&message[strlen(message)], " %d", i+1);
 					break;
 				}
@@ -469,17 +506,19 @@ int Send_NewGame()
 		}
 		Message(message);
 
-		int ready = SDLNet_CheckSockets(SocketSet, 100);
+		now = SDL_GetTicks();
+		for (i = 0; i < gGameInfo.GetNumNodes(); ++i) {
+			if (HasTimedOut(i, now)) {
+				SDLNet_UDP_Send(gNetFD, i+1, &newgame);
+				WaitingAcks[i] = NodeTimeout(now);
+			}
+		}
+		int ready = SDLNet_CheckSockets(SocketSet, NextTimeout(now));
 		if (ready < 0) {
 			error("Network error: SDLNet_CheckSockets()\r\n");
 			return(-1);
 		}
 		if (ready == 0) {
-			for (i = 0; i < MAX_NODES; ++i) {
-				if (WaitingAcks[i]) {
-					SDLNet_UDP_Send(gNetFD, i+1, &newgame);
-				}
-			}
 			continue;
 		}
 
@@ -508,12 +547,12 @@ int Send_NewGame()
 		if (!nodeID) {
 			continue;
 		}
-		for (i = 0; i < MAX_NODES; ++i) {
-			if (nodeID == WaitingAcks[i]) {
-				WaitingAcks[i] = 0;
-				break;
-			}
+		int index = gGameInfo.GetNodeIndex(nodeID);
+		if (index < 0) {
+			/* This must be from an old node */
+			continue;
 		}
+		WaitingAcks[index] = 0;
 	}
 	return(0);
 }