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