1 /*
2  *  network_star_spoke.cpp
3 
4 	Copyright (C) 2003 and beyond by Woody Zenfell, III
5 	and the "Aleph One" developers.
6 
7 	This program is free software; you can redistribute it and/or modify
8 	it under the terms of the GNU General Public License as published by
9 	the Free Software Foundation; either version 3 of the License, or
10 	(at your option) any later version.
11 
12 	This program is distributed in the hope that it will be useful,
13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 	GNU General Public License for more details.
16 
17 	This license is contained in the file "COPYING",
18 	which is included with this source code; it is available online at
19 	http://www.gnu.org/licenses/gpl.html
20 
21  *  The portion of the star game protocol run on every player's machine.
22  *
23  *  Created by Woody Zenfell, III on Fri May 02 2003.
24  *
25  *  May 27, 2003 (Woody Zenfell): lossy byte-stream distribution.
26  *
27  *  June 30, 2003 (Woody Zenfell): lossy byte-stream distribution more tolerant of scheduling jitter
28  *	(i.e. will queue multiple chunks before send, instead of dropping all data but most recent)
29  *
30  *  September 17, 2004 (jkvw):
31  *	NAT-friendly networking - we no longer get spoke addresses form topology -
32  *	instead spokes send identification packets to hub with player ID.
33  *	Hub can then associate the ID in the identification packet with the paket's source address.
34  */
35 
36 #if !defined(DISABLE_NETWORKING)
37 
38 #include "network_star.h"
39 #include "AStream.h"
40 #include "mytm.h"
41 #include "network_private.h" // kPROTOCOL_TYPE
42 #include "WindowedNthElementFinder.h"
43 #include "vbl.h" // parse_keymap
44 #include "CircularByteBuffer.h"
45 #include "Logging.h"
46 #include "crc.h"
47 #include "player.h"
48 #include "InfoTree.h"
49 
50 #include <map>
51 
52 extern void make_player_really_net_dead(size_t inPlayerIndex);
53 extern void call_distribution_response_function_if_available(byte* inBuffer, uint16 inBufferSize, int16 inDistributionType, uint8 inSendingPlayerIndex);
54 
55 
56 enum {
57         kDefaultPregameTicksBeforeNetDeath = 90 * TICKS_PER_SECOND,
58         kDefaultInGameTicksBeforeNetDeath = 5 * TICKS_PER_SECOND,
59         kDefaultOutgoingFlagsQueueSize = TICKS_PER_SECOND / 2,
60         kDefaultRecoverySendPeriod = TICKS_PER_SECOND / 2,
61 	kDefaultTimingWindowSize = 3 * TICKS_PER_SECOND,
62 	kDefaultTimingNthElement = kDefaultTimingWindowSize / 2,
63 	kLossyByteStreamDataBufferSize = 1280,
64 	kTypicalLossyByteStreamChunkSize = 56,
65 	kLossyByteStreamDescriptorCount = kLossyByteStreamDataBufferSize / kTypicalLossyByteStreamChunkSize
66 };
67 
68 struct SpokePreferences
69 {
70 	int32	mPregameTicksBeforeNetDeath;
71 	int32	mInGameTicksBeforeNetDeath;
72 //	int32	mOutgoingFlagsQueueSize;
73 	int32	mRecoverySendPeriod;
74 	int32	mTimingWindowSize;
75 	int32	mTimingNthElement;
76 	bool	mAdjustTiming;
77 };
78 
79 static SpokePreferences sSpokePreferences;
80 
81 static TickBasedActionQueue sOutgoingFlags(kDefaultOutgoingFlagsQueueSize);
82 static TickBasedActionQueue sUnconfirmedFlags(kDefaultOutgoingFlagsQueueSize);
83 static DuplicatingTickBasedCircularQueue<action_flags_t> sLocallyGeneratedFlags;
84 static int32 sSmallestRealGameTick;
85 
86 struct IncomingGameDataPacketProcessingContext {
87         bool mMessagesDone;
88         bool mGotTimingAdjustmentMessage;
89 
IncomingGameDataPacketProcessingContextIncomingGameDataPacketProcessingContext90         IncomingGameDataPacketProcessingContext() : mMessagesDone(false), mGotTimingAdjustmentMessage(false) {}
91 };
92 
93 typedef void (*StarMessageHandler)(AIStream& s, IncomingGameDataPacketProcessingContext& c);
94 typedef std::map<uint16, StarMessageHandler> MessageTypeToMessageHandler;
95 
96 static MessageTypeToMessageHandler sMessageTypeToMessageHandler;
97 
98 static int8 sRequestedTimingAdjustment;
99 static int8 sOutstandingTimingAdjustment;
100 
101 struct NetworkPlayer_spoke {
102         bool				mZombie;
103         bool				mConnected;
104         int32				mNetDeadTick;
105         WritableTickBasedActionQueue* 	mQueue;
106 };
107 
108 static vector<NetworkPlayer_spoke> sNetworkPlayers;
109 static int32 sNetworkTicker;
110 static int32 sLastNetworkTickHeard;
111 static int32 sLastNetworkTickSent;
112 static bool sConnected = false;
113 static bool sSpokeActive = false;
114 static myTMTaskPtr sSpokeTickTask = NULL;
115 static DDPFramePtr sOutgoingFrame = NULL;
116 static DDPPacketBuffer sLocalOutgoingBuffer;
117 static bool sNeedToSendLocalOutgoingBuffer = false;
118 static bool sHubIsLocal = false;
119 static NetAddrBlock sHubAddress;
120 static size_t sLocalPlayerIndex;
121 static int32 sSmallestUnreceivedTick;
122 static WindowedNthElementFinder<int32> sNthElementFinder(kDefaultTimingWindowSize);
123 static bool sTimingMeasurementValid;
124 static int32 sTimingMeasurement;
125 static bool sHeardFromHub = false;
126 
127 static vector<int32> sDisplayLatencyBuffer; // stores the last 30 latency calculations, in ticks
128 static uint32 sDisplayLatencyCount = 0;
129 static int32 sDisplayLatencyTicks = 0; // sum of the latency ticks from the last 30 seconds, using above two
130 
131 static int32 sSmallestUnconfirmedTick;
132 
133 struct SpokeLossyByteStreamChunkDescriptor
134 {
135 	uint16	mLength;
136 	int16	mType;
137 	uint32	mDestinations;
138 };
139 
140 // This holds outgoing lossy byte stream data
141 static CircularByteBuffer sOutgoingLossyByteStreamData(kLossyByteStreamDataBufferSize);
142 
143 // This holds a descriptor for each chunk of lossy byte stream data held in the above buffer
144 static CircularQueue<SpokeLossyByteStreamChunkDescriptor> sOutgoingLossyByteStreamDescriptors(kLossyByteStreamDescriptorCount);
145 
146 // This is currently used only to hold incoming streaming data until it's passed to the upper-level code
147 static byte sScratchBuffer[kLossyByteStreamDataBufferSize];
148 
149 
150 static void spoke_became_disconnected();
151 static void spoke_received_game_data_packet_v1(AIStream& ps, bool reflected_flags);
152 static void spoke_received_ping_request(AIStream& ps, NetAddrBlock address);
153 static void spoke_received_ping_response(AIStream& ps, NetAddrBlock address);
154 static void process_messages(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
155 static void handle_end_of_messages_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
156 static void handle_player_net_dead_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
157 static void handle_timing_adjustment_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
158 static void handle_lossy_byte_stream_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context);
159 static void process_optional_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context, uint16 inMessageType);
160 static bool spoke_tick();
161 static void send_packet();
162 static void send_identification_packet();
163 
164 
165 static inline NetworkPlayer_spoke&
getNetworkPlayer(size_t inIndex)166 getNetworkPlayer(size_t inIndex)
167 {
168         assert(inIndex < sNetworkPlayers.size());
169         return sNetworkPlayers[inIndex];
170 }
171 
172 
173 
174 static inline bool
operator !=(const NetAddrBlock & a,const NetAddrBlock & b)175 operator !=(const NetAddrBlock& a, const NetAddrBlock& b)
176 {
177         return memcmp(&a, &b, sizeof(a)) != 0;
178 }
179 
180 
181 
182 static OSErr
send_frame_to_local_hub(DDPFramePtr frame,NetAddrBlock * address,short protocolType,short port)183 send_frame_to_local_hub(DDPFramePtr frame, NetAddrBlock *address, short protocolType, short port)
184 {
185         sLocalOutgoingBuffer.datagramSize = frame->data_size;
186         memcpy(sLocalOutgoingBuffer.datagramData, frame->data, frame->data_size);
187         sLocalOutgoingBuffer.protocolType = protocolType;
188         // An all-0 sourceAddress is the cue for "local spoke" currently.
189         obj_clear(sLocalOutgoingBuffer.sourceAddress);
190         sNeedToSendLocalOutgoingBuffer = true;
191         return noErr;
192 }
193 
194 
195 
196 static inline void
check_send_packet_to_hub()197 check_send_packet_to_hub()
198 {
199         if(sNeedToSendLocalOutgoingBuffer)
200 	{
201 		logContextNMT("delivering stored packet to local hub");
202                 hub_received_network_packet(&sLocalOutgoingBuffer);
203 	}
204 
205         sNeedToSendLocalOutgoingBuffer = false;
206 }
207 
208 
209 
210 void
spoke_initialize(const NetAddrBlock & inHubAddress,int32 inFirstTick,size_t inNumberOfPlayers,WritableTickBasedActionQueue * const inPlayerQueues[],bool inPlayerConnected[],size_t inLocalPlayerIndex,bool inHubIsLocal)211 spoke_initialize(const NetAddrBlock& inHubAddress, int32 inFirstTick, size_t inNumberOfPlayers, WritableTickBasedActionQueue* const inPlayerQueues[], bool inPlayerConnected[], size_t inLocalPlayerIndex, bool inHubIsLocal)
212 {
213         assert(inNumberOfPlayers >= 1);
214         assert(inLocalPlayerIndex < inNumberOfPlayers);
215         assert(inPlayerQueues[inLocalPlayerIndex] != NULL);
216         assert(inPlayerConnected[inLocalPlayerIndex]);
217 
218         sHubIsLocal = inHubIsLocal;
219         sHubAddress = inHubAddress;
220 
221         sLocalPlayerIndex = inLocalPlayerIndex;
222 
223         sOutgoingFrame = NetDDPNewFrame();
224 
225         sSmallestRealGameTick = inFirstTick;
226         int32 theFirstPregameTick = inFirstTick - kPregameTicks;
227         sOutgoingFlags.reset(theFirstPregameTick);
228 	sUnconfirmedFlags.reset(sSmallestRealGameTick);
229 	sSmallestUnconfirmedTick = sSmallestRealGameTick;
230         sSmallestUnreceivedTick = theFirstPregameTick;
231 
232         sNetworkPlayers.clear();
233         sNetworkPlayers.resize(inNumberOfPlayers);
234 
235         sLocallyGeneratedFlags.children().clear();
236         sLocallyGeneratedFlags.children().insert(&sOutgoingFlags);
237 	sLocallyGeneratedFlags.children().insert(&sUnconfirmedFlags);
238 
239         for(size_t i = 0; i < inNumberOfPlayers; i++)
240         {
241                 sNetworkPlayers[i].mZombie = (inPlayerQueues[i] == NULL);
242                 sNetworkPlayers[i].mConnected = inPlayerConnected[i];
243                 sNetworkPlayers[i].mNetDeadTick = theFirstPregameTick - 1;
244                 sNetworkPlayers[i].mQueue = inPlayerQueues[i];
245                 if(sNetworkPlayers[i].mConnected)
246                 {
247                         sNetworkPlayers[i].mQueue->reset(sSmallestRealGameTick);
248                 }
249         }
250 
251         sRequestedTimingAdjustment = 0;
252         sOutstandingTimingAdjustment = 0;
253 
254         sNetworkTicker = 0;
255         sLastNetworkTickHeard = 0;
256         sLastNetworkTickSent = 0;
257         sConnected = true;
258 	sNthElementFinder.reset(sSpokePreferences.mTimingWindowSize);
259 	sTimingMeasurementValid = false;
260 
261 	sOutgoingLossyByteStreamDescriptors.reset();
262 	sOutgoingLossyByteStreamData.reset();
263 
264         sMessageTypeToMessageHandler.clear();
265         sMessageTypeToMessageHandler[kEndOfMessagesMessageType] = handle_end_of_messages_message;
266         sMessageTypeToMessageHandler[kTimingAdjustmentMessageType] = handle_timing_adjustment_message;
267         sMessageTypeToMessageHandler[kPlayerNetDeadMessageType] = handle_player_net_dead_message;
268 	sMessageTypeToMessageHandler[kHubToSpokeLossyByteStreamMessageType] = handle_lossy_byte_stream_message;
269 
270         sNeedToSendLocalOutgoingBuffer = false;
271 
272         sSpokeActive = true;
273         sSpokeTickTask = myXTMSetup(1000/TICKS_PER_SECOND, spoke_tick);
274 
275 	sDisplayLatencyBuffer.resize(TICKS_PER_SECOND, 0);
276 	sDisplayLatencyCount = 0;
277 	sDisplayLatencyTicks = 0;
278 
279 	sHeardFromHub = false;
280 }
281 
282 
283 
284 void
spoke_cleanup(bool inGraceful)285 spoke_cleanup(bool inGraceful)
286 {
287         // Stop processing incoming packets (packet processor won't start processing another packet
288         // due to sSpokeActive = false, and we know it's not in the middle of processing one because
289         // we take the mutex).
290         if(take_mytm_mutex())
291         {
292 		// Mark the tick task for cancellation (it won't start running again after this returns).
293 		myTMRemove(sSpokeTickTask);
294 		sSpokeTickTask = NULL;
295 
296                 sSpokeActive = false;
297 
298 		// We send one last packet here to try to not leave the hub hanging on our ACK.
299 		send_packet();
300 		check_send_packet_to_hub();
301 
302 		release_mytm_mutex();
303         }
304 
305         // This waits for the tick task to actually finish
306         myTMCleanup(true);
307 
308         sMessageTypeToMessageHandler.clear();
309         sNetworkPlayers.clear();
310         sLocallyGeneratedFlags.children().clear();
311 	sDisplayLatencyBuffer.clear();
312         NetDDPDisposeFrame(sOutgoingFrame);
313         sOutgoingFrame = NULL;
314 }
315 
316 
317 
318 int32
spoke_get_net_time()319 spoke_get_net_time()
320 {
321 	static int32 sPreviousDelay = -1;
322 
323 	int32 theDelay = (sSpokePreferences.mAdjustTiming && sTimingMeasurementValid) ? sTimingMeasurement : 0;
324 
325 	if(theDelay != sPreviousDelay)
326 	{
327 		logDump("local delay is now %d", theDelay);
328 		sPreviousDelay = theDelay;
329 	}
330 
331 	return (sConnected ? sOutgoingFlags.getWriteTick() - theDelay : getNetworkPlayer(sLocalPlayerIndex).mQueue->getWriteTick());
332 }
333 
334 
335 
336 void
spoke_distribute_lossy_streaming_bytes_to_everyone(int16 inDistributionType,byte * inBytes,uint16 inLength,bool inExcludeLocalPlayer,bool onlySendToTeam)337 spoke_distribute_lossy_streaming_bytes_to_everyone(int16 inDistributionType, byte* inBytes, uint16 inLength, bool inExcludeLocalPlayer, bool onlySendToTeam)
338 {
339 
340 	int16 local_team;
341 	if (onlySendToTeam)
342 	{
343 		player_info* player = (player_info *)NetGetPlayerData(sLocalPlayerIndex);
344 		local_team = player->team;
345 	}
346 
347 	uint32 theDestinations = 0;
348 	for(size_t i = 0; i < sNetworkPlayers.size(); i++)
349 	{
350 		if((i != sLocalPlayerIndex || !inExcludeLocalPlayer) && !sNetworkPlayers[i].mZombie && sNetworkPlayers[i].mConnected)
351 		{
352 			if (onlySendToTeam)
353 			{
354 				player_info* player = (player_info *)NetGetPlayerData(i);
355 				if (player->team == local_team)
356 					theDestinations |= (((uint32)1) << i);
357 
358 			}
359 			else
360 			{
361 				theDestinations |= (((uint32)1) << i);
362 			}
363 		}
364 	}
365 
366 	spoke_distribute_lossy_streaming_bytes(inDistributionType, theDestinations, inBytes, inLength);
367 }
368 
369 
370 
371 void
spoke_distribute_lossy_streaming_bytes(int16 inDistributionType,uint32 inDestinationsBitmask,byte * inBytes,uint16 inLength)372 spoke_distribute_lossy_streaming_bytes(int16 inDistributionType, uint32 inDestinationsBitmask, byte* inBytes, uint16 inLength)
373 {
374 	if(inLength > sOutgoingLossyByteStreamData.getRemainingSpace())
375 	{
376 		logNoteNMT("spoke has insufficient buffer space for %hu bytes of outgoing lossy streaming type %hd; discarded", inLength, inDistributionType);
377 		return;
378 	}
379 
380 	if(sOutgoingLossyByteStreamDescriptors.getRemainingSpace() < 1)
381 	{
382 		logNoteNMT("spoke has exhausted descriptor buffer space; discarding %hu bytes of outgoing lossy streaming type %hd", inLength, inDistributionType);
383 		return;
384 	}
385 
386 	struct SpokeLossyByteStreamChunkDescriptor theDescriptor;
387 	theDescriptor.mLength = inLength;
388 	theDescriptor.mDestinations = inDestinationsBitmask;
389 	theDescriptor.mType = inDistributionType;
390 
391 	logDumpNMT("spoke application decided to send %d bytes of lossy streaming type %d destined for players 0x%x", inLength, inDistributionType, inDestinationsBitmask);
392 
393 	sOutgoingLossyByteStreamData.enqueueBytes(inBytes, inLength);
394 	sOutgoingLossyByteStreamDescriptors.enqueue(theDescriptor);
395 }
396 
397 
398 
399 static void
spoke_became_disconnected()400 spoke_became_disconnected()
401 {
402         sConnected = false;
403         for(size_t i = 0; i < sNetworkPlayers.size(); i++)
404         {
405                 if(sNetworkPlayers[i].mConnected)
406                         make_player_really_net_dead(i);
407         }
408 }
409 
410 
411 
412 void
spoke_received_network_packet(DDPPacketBufferPtr inPacket)413 spoke_received_network_packet(DDPPacketBufferPtr inPacket)
414 {
415 	logContextNMT("spoke processing a received packet");
416 
417         // Ignore packets not from our hub
418 //        if(inPacket->sourceAddress != sHubAddress)
419 //                return;
420 
421         try {
422                 AIStreamBE ps(inPacket->datagramData, inPacket->datagramSize);
423 
424 		uint16 thePacketMagic;
425 		ps >> thePacketMagic;
426 
427 		// If we've already given up on the connection, ignore non-ping packets.
428 		if((!sConnected || !sSpokeActive) &&
429 		   thePacketMagic != kPingRequestPacket &&
430 		   thePacketMagic != kPingResponsePacket)
431 			return;
432 
433 		uint16 thePacketCRC;
434 		ps >> thePacketCRC;
435 
436 		// blank out the CRC field before calculating
437 		inPacket->datagramData[2] = 0;
438 		inPacket->datagramData[3] = 0;
439 
440 		if (thePacketCRC != calculate_data_crc_ccitt(inPacket->datagramData, inPacket->datagramSize))
441 		{
442 			logWarningNMT("CRC failure; discarding packet type %i", thePacketMagic);
443 			return;
444 		}
445 
446                 switch(thePacketMagic)
447                 {
448 		case kHubToSpokeGameDataPacketV1Magic:
449 			spoke_received_game_data_packet_v1(ps, false);
450 			break;
451 
452 		case kHubToSpokeGameDataPacketWithSpokeFlagsV1Magic:
453 			spoke_received_game_data_packet_v1(ps, true);
454 			break;
455 
456 		case kPingRequestPacket:
457 			spoke_received_ping_request(ps, inPacket->sourceAddress);
458 			break;
459 
460 		case kPingResponsePacket:
461 			spoke_received_ping_response(ps, inPacket->sourceAddress);
462 			break;
463 
464 		default:
465 			// Ignore unknown packet types
466 			logTraceNMT("unknown packet type %i", thePacketMagic);
467 			break;
468                 }
469         }
470         catch(...)
471         {
472                 // do nothing special, we just ignore the remainder of the packet.
473         }
474 }
475 
476 
477 
478 static void
spoke_received_game_data_packet_v1(AIStream & ps,bool reflected_flags)479 spoke_received_game_data_packet_v1(AIStream& ps, bool reflected_flags)
480 {
481 	sHeardFromHub = true;
482 
483         IncomingGameDataPacketProcessingContext context;
484 
485         // Piggybacked ACK
486         int32 theSmallestUnacknowledgedTick;
487         ps >> theSmallestUnacknowledgedTick;
488 
489 	// we can get an early ACK only if the server made up flags for us...
490 	if (theSmallestUnacknowledgedTick > sOutgoingFlags.getWriteTick())
491 	{
492 		if (reflected_flags)
493 		{
494 			theSmallestUnacknowledgedTick = sOutgoingFlags.getWriteTick();
495 		}
496 		else
497 		{
498 			logTraceNMT("early ack (%d > %d)", theSmallestUnacknowledgedTick, sOutgoingFlags.getWriteTick());
499 			return;
500 		}
501 	}
502 
503 
504         // Heard from hub
505         sLastNetworkTickHeard = sNetworkTicker;
506 
507         // Remove acknowledged elements from outgoing queue
508         for(int tick = sOutgoingFlags.getReadTick(); tick < theSmallestUnacknowledgedTick; tick++)
509 	{
510 		logTraceNMT("dequeueing tick %d from sOutgoingFlags", tick);
511                 sOutgoingFlags.dequeue();
512 	}
513 
514         // Process messages
515         process_messages(ps, context);
516 
517         if(!context.mGotTimingAdjustmentMessage)
518 	{
519 		if(sRequestedTimingAdjustment != 0)
520 			logTraceNMT("timing adjustment no longer requested");
521 
522                 sRequestedTimingAdjustment = 0;
523 	}
524 
525         // Action_flags!!!
526         // If there aren't any, we're done
527         if(ps.tellg() >= ps.maxg())
528 	{
529 		// If we are the only connected player, well because of the way the loop below works,
530 		// we have to enqueue net_dead flags for the other players now, up to match the most
531 		// recent tick the hub has acknowledged.
532 		// We can't assume we are the only connected player merely by virtue of there being no
533 		// flags in the packet.  The hub could be waiting on somebody else to send and is
534 		// essentially sending us "keep-alive" packets (which could contain no new flags) in the meantime.
535 		// In other words, (we are alone) implies (no flags in packet), but not the converse.
536 		// Here "alone" means there are no other players who are connected or who will be netdead
537 		// sometime in the future.  (If their NetDeadTick is greater than the ACKed tick, we expect
538 		// that the hub will be sending actual flags in the future to make up the difference.)
539 		bool weAreAlone = true;
540 		int32 theSmallestUnacknowledgedTick = sOutgoingFlags.getReadTick();
541 		for(size_t i = 0; i < sNetworkPlayers.size(); i++)
542 		{
543 			if(i != sLocalPlayerIndex && (sNetworkPlayers[i].mConnected || sNetworkPlayers[i].mNetDeadTick > theSmallestUnacknowledgedTick))
544 			{
545 				weAreAlone = false;
546 				break;
547 			}
548 		}
549 
550 		if(weAreAlone)
551 		{
552 			logContextNMT("handling special \"we are alone\" case");
553 
554 			for(size_t i = 0; i < sNetworkPlayers.size(); i++)
555 			{
556 				NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
557 				if (i == sLocalPlayerIndex)
558 				{
559 					while (sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick())
560 					{
561 						sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick++));
562 					}
563 				}
564 				else if (!thePlayer.mZombie)
565 				{
566 					while(thePlayer.mQueue->getWriteTick() < theSmallestUnacknowledgedTick)
567 					{
568 						logDumpNMT("enqueued NET_DEAD_ACTION_FLAG for player %d tick %d", i, thePlayer.mQueue->getWriteTick());
569 						thePlayer.mQueue->enqueue(static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG));
570 					}
571 				}
572 			}
573 
574 			sSmallestUnreceivedTick = theSmallestUnacknowledgedTick;
575 			logDumpNMT("sSmallestUnreceivedTick is now %d", sSmallestUnreceivedTick);
576 		}
577 
578                 return;
579 	} // no data left in packet
580 
581         int32 theSmallestUnreadTick;
582         ps >> theSmallestUnreadTick;
583 
584         // Can't accept packets that skip ticks
585         if(theSmallestUnreadTick > sSmallestUnreceivedTick)
586 	{
587 		logTraceNMT("early flags (%d > %d)", theSmallestUnreadTick, sSmallestUnreceivedTick);
588                 return;
589 	}
590 
591         // Figure out how many ticks of flags we can actually enqueue
592         // We want to stock all queues evenly, since we ACK everyone's flags for a tick together.
593         int theSmallestQueueSpace = INT_MAX;
594         for(size_t i = 0; i < sNetworkPlayers.size(); i++)
595         {
596 		// we'll never get flags for zombies, and we're not expected
597 		// to enqueue flags for zombies
598                 if(sNetworkPlayers[i].mZombie)
599                         continue;
600 
601                 int theQueueSpace = sNetworkPlayers[i].mQueue->availableCapacity();
602 
603                 /*
604                         hmm, taking this exemption out, because we will start enqueueing PLAYER_NET_DEAD_FLAG onto the queue.
605                 // If player is netdead or will become netdead before queue fills,
606                 // player's queue space will not limit us
607                 if(!sNetworkPlayers[i].mConnected)
608                 {
609                         int theRemainingLiveTicks = sNetworkPlayers[i].mNetDeadTick - sSmallestUnreceivedTick;
610                         if(theRemainingLiveTicks < theQueueSpace)
611                                 continue;
612                 }
613                  */
614 
615                 if(theQueueSpace < theSmallestQueueSpace)
616                         theSmallestQueueSpace = theQueueSpace;
617         }
618 
619 	logDumpNMT("%d queue space available", theSmallestQueueSpace);
620 
621         // Read and enqueue the actual action_flags from the packet
622         // The body of this loop is a bit more convoluted than you might
623         // expect, because the same loop is used to skip already-seen action_flags
624         // and to enqueue new ones.
625 	while(ps.tellg() < ps.maxg())
626         {
627                 // If we've no room to enqueue stuff, no point in finishing reading the packet.
628                 if(theSmallestQueueSpace <= 0)
629                         break;
630 
631                 for(size_t i = 0; i < sNetworkPlayers.size(); i++)
632                 {
633 
634 			// We'll never get flags for zombies
635 			if (sNetworkPlayers[i].mZombie)
636 				continue;
637 
638 			// if our own flags are not sent back to us,
639 			// confirm the ones we have in our unconfirmed queue,
640 			// and do not read any from the packet
641 			if (i == sLocalPlayerIndex && !reflected_flags)
642 			{
643 				if (theSmallestUnreadTick == sSmallestUnreceivedTick && theSmallestUnreadTick >= sSmallestRealGameTick)
644 				{
645 					assert(sNetworkPlayers[i].mQueue->getWriteTick() == sSmallestUnconfirmedTick);
646 					assert(sSmallestUnconfirmedTick >= sUnconfirmedFlags.getReadTick());
647 					assert(sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick());
648 					// confirm this flag
649 					sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick));
650 					sSmallestUnconfirmedTick++;
651 				}
652 
653 				continue;
654 			}
655 
656                         bool shouldEnqueueNetDeadFlags = false;
657 
658                         // We won't get flags for netdead players
659                         NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
660                         if(!thePlayer.mConnected)
661                         {
662                                 if(thePlayer.mNetDeadTick < theSmallestUnreadTick)
663                                         shouldEnqueueNetDeadFlags = true;
664 
665                                 if(thePlayer.mNetDeadTick == theSmallestUnreadTick)
666                                 {
667                                         // Only actually act if this tick is new to us
668                                         if(theSmallestUnreadTick == sSmallestUnreceivedTick)
669                                                 make_player_really_net_dead(i);
670                                         shouldEnqueueNetDeadFlags = true;
671                                 }
672                         }
673 
674                         // Decide what flags are appropriate for this player this tick
675                         action_flags_t theFlags;
676                         if(shouldEnqueueNetDeadFlags)
677                                 // We effectively generate a tick's worth of flags in lieu of reading it from the packet.
678                                 theFlags = static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG);
679                         else
680 			{
681                                 // We should have a flag for this player for this tick!
682 				try
683 				{
684 					ps >> theFlags;
685 				}
686 				catch (const AStream::failure& f)
687 				{
688 					logWarningNMT("AStream exception (%s) for player %i at theSmallestUnreadTick %i! OOS is likely!\n", f.what(), i, theSmallestUnreadTick);
689 					return;
690 				}
691 			}
692 
693 
694                         // Now, we've gotten flags, probably from the packet... should we enqueue them?
695                         if(theSmallestUnreadTick == sSmallestUnreceivedTick)
696                         {
697 				if(theSmallestUnreadTick >= sSmallestRealGameTick)
698 				{
699 					WritableTickBasedActionQueue& theQueue = *(sNetworkPlayers[i].mQueue);
700 					assert(theQueue.getWriteTick() == sSmallestUnreceivedTick);
701 					assert(theQueue.availableCapacity() > 0);
702 					logTraceNMT("enqueueing flags %x for player %d tick %d", theFlags, i, theQueue.getWriteTick());
703 					theQueue.enqueue(theFlags);
704 					if (i == sLocalPlayerIndex) sSmallestUnconfirmedTick++;
705 				}
706                         }
707 
708                 } // iterate over players
709 
710 		theSmallestUnreadTick++;
711 		if(sSmallestUnreceivedTick < theSmallestUnreadTick)
712 		{
713 			theSmallestQueueSpace--;
714 			sSmallestUnreceivedTick = theSmallestUnreadTick;
715 
716 			int32 theLatencyMeasurement = sOutgoingFlags.getWriteTick() - sSmallestUnreceivedTick;
717 			logDumpNMT("latency measurement: %d", theLatencyMeasurement);
718 
719 			sNthElementFinder.insert(theLatencyMeasurement);
720 			// We capture these values here so we don't have to take a lock in GetNetTime.
721 			sTimingMeasurementValid = sNthElementFinder.window_full();
722 			if(sTimingMeasurementValid)
723 				sTimingMeasurement = sNthElementFinder.nth_largest_element(sSpokePreferences.mTimingNthElement);
724 
725 			// update the latency display
726 			sDisplayLatencyTicks -= sDisplayLatencyBuffer[sDisplayLatencyCount % sDisplayLatencyBuffer.size()];
727 			sDisplayLatencyBuffer[sDisplayLatencyCount++ % sDisplayLatencyBuffer.size()] = theLatencyMeasurement;
728 			sDisplayLatencyTicks += theLatencyMeasurement;
729 		}
730 
731 	} // loop while there's packet data left
732 
733 }
734 
735 
736 static void
spoke_received_ping_request(AIStream & ps,NetAddrBlock address)737 spoke_received_ping_request(AIStream& ps, NetAddrBlock address)
738 {
739 	uint16 pingIdentifier;
740 	ps >> pingIdentifier;
741 
742 	// respond back to requestor
743 	bool initedFrame = false;
744 	if (!sOutgoingFrame)
745 	{
746 		sOutgoingFrame = NetDDPNewFrame();
747 		initedFrame = true;
748 	}
749 
750 	AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
751 	AOStreamBE ops(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
752 
753 	try {
754 		hdr << (uint16)kPingResponsePacket;
755 		ops << pingIdentifier;
756 
757 		// blank out the CRC field before calculating
758 		sOutgoingFrame->data[2] = 0;
759 		sOutgoingFrame->data[3] = 0;
760 
761 		uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ops.tellp());
762 		hdr << crc;
763 
764 		// Send the packet
765 		sOutgoingFrame->data_size = ops.tellp();
766 		NetDDPSendFrame(sOutgoingFrame, &address, kPROTOCOL_TYPE, 0 /* ignored */);
767 	} catch (...) {
768 		logWarningNMT("Caught exception while constructing/sending ping response packet");
769 	}
770 
771 	if (initedFrame)
772 	{
773 		NetDDPDisposeFrame(sOutgoingFrame);
774 		sOutgoingFrame = NULL;
775 	}
776 } // spoke_received_ping_request()
777 
778 
779 static void
spoke_received_ping_response(AIStream & ps,NetAddrBlock address)780 spoke_received_ping_response(AIStream& ps, NetAddrBlock address)
781 {
782 	uint16 pingIdentifier;
783 	ps >> pingIdentifier;
784 
785 	// we don't send ping requests, so we don't expect to get one
786 	logWarningNMT("Received unexpected ping response packet");
787 
788 } // spoke_received_ping_response()
789 
790 
791 static void
process_messages(AIStream & ps,IncomingGameDataPacketProcessingContext & context)792 process_messages(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
793 {
794         while(!context.mMessagesDone)
795         {
796                 uint16 theMessageType;
797                 ps >> theMessageType;
798 
799                 MessageTypeToMessageHandler::iterator i = sMessageTypeToMessageHandler.find(theMessageType);
800 
801                 if(i == sMessageTypeToMessageHandler.end())
802                         process_optional_message(ps, context, theMessageType);
803                 else
804                         i->second(ps, context);
805         }
806 }
807 
808 
809 
810 static void
handle_end_of_messages_message(AIStream & ps,IncomingGameDataPacketProcessingContext & context)811 handle_end_of_messages_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
812 {
813         context.mMessagesDone = true;
814 }
815 
816 
817 
818 static void
handle_player_net_dead_message(AIStream & ps,IncomingGameDataPacketProcessingContext & context)819 handle_player_net_dead_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
820 {
821         uint8 thePlayerIndex;
822         int32 theTick;
823 
824         ps >> thePlayerIndex >> theTick;
825 
826         if(thePlayerIndex > sNetworkPlayers.size())
827                 return;
828 
829         sNetworkPlayers[thePlayerIndex].mConnected = false;
830         sNetworkPlayers[thePlayerIndex].mNetDeadTick = theTick;
831 
832 	logDumpNMT("netDead message: player %d in tick %d", thePlayerIndex, theTick);
833 }
834 
835 
836 
837 static void
handle_timing_adjustment_message(AIStream & ps,IncomingGameDataPacketProcessingContext & context)838 handle_timing_adjustment_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
839 {
840         int8 theAdjustment;
841 
842         ps >> theAdjustment;
843 
844         if(theAdjustment != sRequestedTimingAdjustment)
845         {
846                 sOutstandingTimingAdjustment = theAdjustment;
847                 sRequestedTimingAdjustment = theAdjustment;
848 		logTraceNMT("new timing adjustment message; requested: %d outstanding: %d", sRequestedTimingAdjustment, sOutstandingTimingAdjustment);
849         }
850 
851         context.mGotTimingAdjustmentMessage = true;
852 }
853 
854 
855 
856 static void
handle_lossy_byte_stream_message(AIStream & ps,IncomingGameDataPacketProcessingContext & context)857 handle_lossy_byte_stream_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context)
858 {
859 	uint16 theMessageLength;
860 	ps >> theMessageLength;
861 
862 	size_t theStartOfMessage = ps.tellg();
863 
864 	int16 theDistributionType;
865 	uint8 theSendingPlayer;
866 	ps >> theDistributionType >> theSendingPlayer;
867 
868 	uint16 theDataLength = theMessageLength - (ps.tellg() - theStartOfMessage);
869 	uint16 theSpilloverDataLength = 0;
870 	if(theDataLength > sizeof(sScratchBuffer))
871 	{
872 		logNoteNMT("received too many bytes (%d) of lossy streaming data type %d from player %d; truncating", theDataLength, theDistributionType, theSendingPlayer);
873 		theSpilloverDataLength = theDataLength - sizeof(sScratchBuffer);
874 		theDataLength = sizeof(sScratchBuffer);
875 	}
876 	ps.read(sScratchBuffer, theDataLength);
877 	ps.ignore(theSpilloverDataLength);
878 
879 	logDumpNMT("received %d bytes of lossy streaming type %d data from player %d", theDataLength, theDistributionType, theSendingPlayer);
880 
881 	call_distribution_response_function_if_available(sScratchBuffer, theDataLength, theDistributionType, theSendingPlayer);
882 }
883 
884 
885 
886 static void
process_optional_message(AIStream & ps,IncomingGameDataPacketProcessingContext & context,uint16 inMessageType)887 process_optional_message(AIStream& ps, IncomingGameDataPacketProcessingContext& context, uint16 inMessageType)
888 {
889         // We don't know of any optional messages, so we just skip any we encounter.
890         // (All optional messages are required to encode their length (not including the
891         // space required for the message type or length) in the two bytes immediately
892         // following the message type.)
893         uint16 theLength;
894         ps >> theLength;
895 
896         ps.ignore(theLength);
897 }
898 
899 
900 
901 static bool
spoke_tick()902 spoke_tick()
903 {
904 	logContextNMT("processing spoke_tick %d", sNetworkTicker);
905 
906         sNetworkTicker++;
907 
908         if(sConnected)
909         {
910                 int32 theSilentTicksBeforeNetDeath = (sOutgoingFlags.getReadTick() >= sSmallestRealGameTick) ? sSpokePreferences.mInGameTicksBeforeNetDeath : sSpokePreferences.mPregameTicksBeforeNetDeath;
911 
912                 if(sNetworkTicker - sLastNetworkTickHeard > theSilentTicksBeforeNetDeath)
913                 {
914 			logTraceNMT("giving up on hub; disconnecting");
915                         spoke_became_disconnected();
916                         return true;
917                 }
918         }
919 
920         bool shouldSend = false;
921 
922         // Negative timing adjustment means we need to provide extra ticks because we're late.
923         // We let this cover the normal timing adjustment = 0 case too.
924         if(sOutstandingTimingAdjustment <= 0)
925         {
926                 int theNumberOfFlagsToProvide = -sOutstandingTimingAdjustment + 1;
927 
928 		logDumpNMT("want to provide %d flags", theNumberOfFlagsToProvide);
929 
930                 while(theNumberOfFlagsToProvide > 0)
931 		{
932 
933 			// If we're not connected, write only to our local game's queue.
934 			// If we are connected,
935 			//	if we're actually in the game, write to both local game and outbound flags queues
936 			//	else (if pregame), write only to the outbound flags queue.
937 
938 			WritableTickBasedActionQueue& theTargetQueue =
939 				sConnected ?
940 					((sOutgoingFlags.getWriteTick() >= sSmallestRealGameTick) ?
941 						static_cast<WritableTickBasedActionQueue&>(sLocallyGeneratedFlags)
942 						: static_cast<WritableTickBasedActionQueue&>(sOutgoingFlags))
943 					: *(sNetworkPlayers[sLocalPlayerIndex].mQueue);
944 
945 			if(theTargetQueue.availableCapacity() <= 0)
946 				break;
947 
948 			logDumpNMT("enqueueing flags for tick %d", theTargetQueue.getWriteTick());
949 
950 			theTargetQueue.enqueue(parse_keymap());
951 			shouldSend = true;
952 			theNumberOfFlagsToProvide--;
953 		}
954 
955 		// Prevent creeping timing adjustment during "lulls"; OTOH remember to
956 		// finish next time if we made progress but couldn't complete our obligation.
957 		if(theNumberOfFlagsToProvide != -sOutstandingTimingAdjustment + 1)
958 			sOutstandingTimingAdjustment = -theNumberOfFlagsToProvide;
959 	}
960         // Positive timing adjustment means we should delay sending for a while,
961         // so we just throw away this local tick.
962         else
963 	{
964 		logDumpNMT("ignoring this tick for timing adjustment");
965                 sOutstandingTimingAdjustment--;
966 	}
967 
968 	logDumpNMT("sOutstandingTimingAdjustment is now %d", sOutstandingTimingAdjustment);
969 
970 	if(sOutgoingLossyByteStreamDescriptors.getCountOfElements() > 0)
971 		shouldSend = true;
972 
973         // If we're connected and (we generated new data or if it's been long enough since we last sent), send.
974         if(sConnected)
975 	{
976 		if (sHeardFromHub) {
977 			if(shouldSend || (sNetworkTicker - sLastNetworkTickSent) >= sSpokePreferences.mRecoverySendPeriod)
978 				send_packet();
979 		} else {
980 			if (!(sNetworkTicker % 30))
981 				send_identification_packet();
982 		}
983 	}
984 	else
985 	{
986 		int32 theLocalPlayerWriteTick = getNetworkPlayer(sLocalPlayerIndex).mQueue->getWriteTick();
987 
988 		// Since we're not connected, we won't be enqueueing flags for the other players in the packet handler.
989 		// So, we do it here to keep the game moving.
990 		for(size_t i = 0; i < sNetworkPlayers.size(); i++)
991 		{
992 			if(i == sLocalPlayerIndex)
993 			{
994 				// move our flags from sent queue to player queue
995 				while (sSmallestUnconfirmedTick < sUnconfirmedFlags.getWriteTick())
996 				{
997 					sNetworkPlayers[i].mQueue->enqueue(sUnconfirmedFlags.peek(sSmallestUnconfirmedTick++));
998 				}
999 				continue;
1000 			}
1001 
1002 			NetworkPlayer_spoke& thePlayer = sNetworkPlayers[i];
1003 
1004 			if(!thePlayer.mZombie)
1005 			{
1006 				while(thePlayer.mQueue->getWriteTick() < theLocalPlayerWriteTick)
1007 				{
1008 					logDumpNMT("enqueueing NET_DEAD_ACTION_FLAG for player %d tick %d", i, thePlayer.mQueue->getWriteTick());
1009 					thePlayer.mQueue->enqueue(static_cast<action_flags_t>(NET_DEAD_ACTION_FLAG));
1010 				}
1011 			}
1012 		}
1013 	}
1014 
1015         check_send_packet_to_hub();
1016 
1017         // We want to run again.
1018         return true;
1019 }
1020 
1021 
1022 
1023 static void
send_packet()1024 send_packet()
1025 {
1026         try {
1027 		AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
1028                 AOStreamBE ps(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
1029 
1030                 // Packet type
1031                 hdr << (uint16)kSpokeToHubGameDataPacketV1Magic;
1032 
1033                 // Acknowledgement
1034                 ps << sSmallestUnreceivedTick;
1035 
1036                 // Messages
1037 		// Outstanding lossy streaming bytes?
1038 		if(sOutgoingLossyByteStreamDescriptors.getCountOfElements() > 0)
1039 		{
1040 			// Note: we make a conscious decision here to dequeue these things before
1041 			// writing to ps, so that if the latter operation exhausts ps's buffer and
1042 			// throws, we have less data to mess with next time, and shouldn't end up
1043 			// throwing every time we try to send here.
1044 			// If we eventually got smarter about managing packet space, we could try
1045 			// harder to preserve and pace data - e.g. change the 'if' immediately before this
1046 			// comment to a 'while', only put in as much data as we think we can fit, etc.
1047 			SpokeLossyByteStreamChunkDescriptor theDescriptor = sOutgoingLossyByteStreamDescriptors.peek();
1048 			sOutgoingLossyByteStreamDescriptors.dequeue();
1049 
1050 			uint16 theMessageLength = theDescriptor.mLength + sizeof(theDescriptor.mType) + sizeof(theDescriptor.mDestinations);
1051 
1052 			ps << (uint16)kSpokeToHubLossyByteStreamMessageType
1053 				<< theMessageLength
1054 				<< theDescriptor.mType
1055 				<< theDescriptor.mDestinations;
1056 
1057 			// XXX unnecessary copy due to overly restrictive interfaces (retaining for clarity)
1058 			assert(theDescriptor.mLength <= sizeof(sScratchBuffer));
1059 			sOutgoingLossyByteStreamData.peekBytes(sScratchBuffer, theDescriptor.mLength);
1060 			sOutgoingLossyByteStreamData.dequeue(theDescriptor.mLength);
1061 
1062 			ps.write(sScratchBuffer, theDescriptor.mLength);
1063 		}
1064 
1065                 // No more messages
1066                 ps << (uint16)kEndOfMessagesMessageType;
1067 
1068                 // Action_flags!!!
1069                 if(sOutgoingFlags.size() > 0)
1070                 {
1071                         ps << sOutgoingFlags.getReadTick();
1072                         for(int32 tick = sOutgoingFlags.getReadTick(); tick < sOutgoingFlags.getWriteTick(); tick++)
1073                                 ps << sOutgoingFlags.peek(tick);
1074                 }
1075 
1076 		logDumpNMT("preparing to send packet: ACK %d, flags [%d,%d)", sSmallestUnreceivedTick, sOutgoingFlags.getReadTick(), sOutgoingFlags.getWriteTick());
1077 
1078 		// blank out the CRC before calculating it
1079 		sOutgoingFrame->data[2] = 0;
1080 		sOutgoingFrame->data[3] = 0;
1081 
1082 		uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ps.tellp());
1083 		hdr << crc;
1084 
1085                 // Send the packet
1086                 sOutgoingFrame->data_size = ps.tellp();
1087 
1088                 if(sHubIsLocal)
1089                         send_frame_to_local_hub(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1090                 else
1091                         NetDDPSendFrame(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1092 
1093                 sLastNetworkTickSent = sNetworkTicker;
1094         }
1095         catch (...) {
1096         }
1097 }
1098 
1099 
1100 
1101 static void
send_identification_packet()1102 send_identification_packet()
1103 {
1104         try {
1105 		AOStreamBE hdr(sOutgoingFrame->data, kStarPacketHeaderSize);
1106                 AOStreamBE ps(sOutgoingFrame->data, ddpMaxData, kStarPacketHeaderSize);
1107 
1108 		// Message type
1109 		hdr << (uint16) kSpokeToHubIdentification;
1110 
1111                 // ID
1112                 ps << (uint16)sLocalPlayerIndex;
1113 
1114 		// blank out the CRC field before calculating
1115 		sOutgoingFrame->data[2] = 0;
1116 		sOutgoingFrame->data[3] = 0;
1117 
1118 		uint16 crc = calculate_data_crc_ccitt(sOutgoingFrame->data, ps.tellp());
1119 		hdr << crc;
1120 
1121                 // Send the packet
1122                 sOutgoingFrame->data_size = ps.tellp();
1123                 if(sHubIsLocal)
1124                         send_frame_to_local_hub(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1125                 else
1126                         NetDDPSendFrame(sOutgoingFrame, &sHubAddress, kPROTOCOL_TYPE, 0 /* ignored */);
1127         }
1128         catch (...) {
1129         }
1130 }
1131 
spoke_latency()1132 int32 spoke_latency()
1133 {
1134 	return (sDisplayLatencyCount >= TICKS_PER_SECOND) ? sDisplayLatencyTicks * 1000 / TICKS_PER_SECOND / sDisplayLatencyBuffer.size() : NetworkStats::invalid;
1135 }
1136 
spoke_get_unconfirmed_flags_queue()1137 TickBasedActionQueue* spoke_get_unconfirmed_flags_queue()
1138 {
1139 	return &sUnconfirmedFlags;
1140 }
1141 
spoke_get_smallest_unconfirmed_tick()1142 int32 spoke_get_smallest_unconfirmed_tick()
1143 {
1144 	return sSmallestUnconfirmedTick;
1145 }
1146 
1147 
1148 enum {
1149 	kPregameTicksBeforeNetDeathAttribute,
1150 	kInGameTicksBeforeNetDeathAttribute,
1151 	// kOutgoingFlagsQueueSizeAttribute,
1152 	kRecoverySendPeriodAttribute,
1153 	kTimingWindowSizeAttribute,
1154 	kTimingNthElementAttribute,
1155 	kNumInt32Attributes,
1156 	kAdjustTimingAttribute = kNumInt32Attributes,
1157 	kNumAttributes
1158 };
1159 
1160 static const char* sAttributeStrings[kNumInt32Attributes] =
1161 {
1162 	"pregame_ticks_before_net_death",
1163 	"ingame_ticks_before_net_death",
1164 //	"outgoing_flags_queue_size",
1165 	"recovery_send_period",
1166 	"timing_window_size",
1167 	"timing_nth_element"
1168 };
1169 
1170 static int32* sAttributeDestinations[kNumInt32Attributes] =
1171 {
1172 	&sSpokePreferences.mPregameTicksBeforeNetDeath,
1173 	&sSpokePreferences.mInGameTicksBeforeNetDeath,
1174 //	&sSpokePreferences.mOutgoingFlagsQueueSize,
1175 	&sSpokePreferences.mRecoverySendPeriod,
1176 	&sSpokePreferences.mTimingWindowSize,
1177 	&sSpokePreferences.mTimingNthElement
1178 };
1179 
1180 
SpokeParsePreferencesTree(InfoTree prefs,std::string version)1181 void SpokeParsePreferencesTree(InfoTree prefs, std::string version)
1182 {
1183 	for (size_t i = 0; i < kNumInt32Attributes; ++i)
1184 	{
1185 		int32 value = *(sAttributeDestinations[i]);
1186 		if (prefs.read_attr(sAttributeStrings[i], value))
1187 		{
1188 			int32 min = INT32_MIN;
1189 			switch (i) {
1190 				case kPregameTicksBeforeNetDeathAttribute:
1191 				case kInGameTicksBeforeNetDeathAttribute:
1192 				case kRecoverySendPeriodAttribute:
1193 				case kTimingWindowSizeAttribute:
1194 					min = 1;
1195 					break;
1196 				case kTimingNthElementAttribute:
1197 					min = 0;
1198 					break;
1199 			}
1200 			if (value < min)
1201 				logWarning("improper value %d for attribute %s of <spoke>; must be at least %d. using default of %d", value, sAttributeStrings[i], min, *(sAttributeDestinations[i]));
1202 			else
1203 				*(sAttributeDestinations[i]) = value;
1204 		}
1205 	}
1206 
1207 	prefs.read_attr("adjust_timing", sSpokePreferences.mAdjustTiming);
1208 
1209 
1210 	// The checks above are not sufficient to catch all bad cases; if user specified a window size
1211 	// smaller than default, this is our only chance to deal with it.
1212 	if(sSpokePreferences.mTimingNthElement >= sSpokePreferences.mTimingWindowSize)
1213 	{
1214 		logWarning("value for <spoke> attribute %s (%d) must be less than value for %s (%d).  using %d", sAttributeStrings[kTimingNthElementAttribute], sSpokePreferences.mTimingNthElement, sAttributeStrings[kTimingWindowSizeAttribute], sSpokePreferences.mTimingWindowSize, sSpokePreferences.mTimingWindowSize - 1);
1215 
1216 		sSpokePreferences.mTimingNthElement = sSpokePreferences.mTimingWindowSize - 1;
1217 	}
1218 }
1219 
1220 
SpokePreferencesTree()1221 InfoTree SpokePreferencesTree()
1222 {
1223 	InfoTree root;
1224 
1225 	for (size_t i = 0; i < kNumInt32Attributes; ++i)
1226 		root.put_attr(sAttributeStrings[i], *(sAttributeDestinations[i]));
1227 	root.put_attr("adjust_timing", sSpokePreferences.mAdjustTiming);
1228 
1229 	return root;
1230 }
1231 
1232 
1233 
1234 void
DefaultSpokePreferences()1235 DefaultSpokePreferences()
1236 {
1237 	sSpokePreferences.mPregameTicksBeforeNetDeath = kDefaultPregameTicksBeforeNetDeath;
1238 	sSpokePreferences.mInGameTicksBeforeNetDeath = kDefaultInGameTicksBeforeNetDeath;
1239 	//	sSpokePreferences.mOutgoingFlagsQueueSize = kDefaultOutgoingFlagsQueueSize;
1240 	sSpokePreferences.mRecoverySendPeriod = kDefaultRecoverySendPeriod;
1241 	sSpokePreferences.mTimingWindowSize = kDefaultTimingWindowSize;
1242 	sSpokePreferences.mTimingNthElement = kDefaultTimingNthElement;
1243 	sSpokePreferences.mAdjustTiming = true;
1244 }
1245 
1246 #endif // !defined(DISABLE_NETWORKING)
1247