hifiMoveList.cpp
Engine/source/T3D/gameBase/hifi/hifiMoveList.cpp
Public Defines
define
Public Variables
Detailed Description
Public Defines
MAX_MOVE_PACKET_SENDS() 4
Public Variables
const U32 DefaultMaxMoveSizeList
const F32 DefaultMoveListSizeSlack
const F32 DefaultSmoothMoveAvg
const U32 DefaultTargetMoveListSize
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "T3D/gameBase/hifi/hifiMoveList.h" 25#include "T3D/gameBase/hifi/hifiGameProcess.h" 26#include "T3D/gameBase/gameConnection.h" 27#include "core/stream/bitStream.h" 28 29#define MAX_MOVE_PACKET_SENDS 4 30 31const U32 DefaultTargetMoveListSize = 3; 32const U32 DefaultMaxMoveSizeList = 5; 33const F32 DefaultSmoothMoveAvg = 0.15f; 34const F32 DefaultMoveListSizeSlack = 1.0f; 35 36HifiMoveList::HifiMoveList() 37{ 38 mLastSentMove = 0; 39 mAvgMoveQueueSize = DefaultTargetMoveListSize ; 40 mTargetMoveListSize = DefaultTargetMoveListSize; 41 mMaxMoveListSize = DefaultMaxMoveSizeList; 42 mSmoothMoveAvg = DefaultSmoothMoveAvg; 43 mMoveListSizeSlack = DefaultMoveListSizeSlack; 44 mTotalServerTicks = ServerTicksUninitialized; 45 mSuppressMove = false; 46} 47 48void HifiMoveList::updateClientServerTickDiff(S32 & tickDiff) 49{ 50 if (mLastMoveAck==0) 51 tickDiff=0; 52 53 // Make adjustments to move list to account for tick mis-matches between client and server. 54 if (tickDiff>0) 55 { 56 // Server ticked more than client. Adjust for this by reseting all hifi objects 57 // to a later position in the tick cache (see ageTickCache below) and at the same 58 // time pulling back some moves we thought we had made (so that time on client 59 // doesn't change). 60 S32 dropTicks = tickDiff; 61 while (dropTicks) 62 { 63#ifdef TORQUE_DEBUG_NET_MOVES 64 Con::printf("dropping move%s",mLastClientMove>mFirstMoveIndex ? "" : " but none there"); 65#endif 66 if (mLastClientMove>mFirstMoveIndex) 67 mLastClientMove--; 68 else 69 tickDiff--; 70 dropTicks--; 71 } 72 AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request"); 73 AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move."); 74 } 75 else 76 { 77 // Client ticked more than server. Adjust for this by taking extra moves 78 // (either adding back moves that were dropped above, or taking new ones). 79 for (S32 i=0; i<-tickDiff; i++) 80 { 81 if (mMoveVec.size() > mLastClientMove - mFirstMoveIndex) 82 { 83#ifdef TORQUE_DEBUG_NET_MOVES 84 Con::printf("add back move"); 85#endif 86 mLastClientMove++; 87 } 88 else 89 { 90#ifdef TORQUE_DEBUG_NET_MOVES 91 Con::printf("add back move -- create one"); 92#endif 93 collectMove(); 94 mLastClientMove++; 95 } 96 } 97 } 98 99 // drop moves that are not made yet (because we rolled them back) and not yet sent 100 U32 len = getMax(mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex); 101 mMoveVec.setSize(len); 102 103#ifdef TORQUE_DEBUG_NET_MOVES 104 Con::printf("move list size: %i, last move: %i, last sent: %i",mMoveVec.size(),mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex); 105#endif 106} 107 108S32 HifiMoveList::getServerTicks(U32 serverTickNum) 109{ 110 S32 serverTicks=0; 111 if (serverTicksInitialized()) 112 { 113 // handle tick wrapping... 114 const S32 MaxTickCount = (1<<TotalTicksBits); 115 const S32 HalfMaxTickCount = MaxTickCount>>1; 116 U32 prevTickNum = mTotalServerTicks & TotalTicksMask; 117 serverTicks = serverTickNum-prevTickNum; 118 if (serverTicks>HalfMaxTickCount) 119 serverTicks -= MaxTickCount; 120 else if (-serverTicks>HalfMaxTickCount) 121 serverTicks += MaxTickCount; 122 AssertFatal(serverTicks>=0,"Server can't tick backwards!!!"); 123 if (serverTicks<0) 124 serverTicks=0; 125 } 126 mTotalServerTicks = serverTickNum; 127 return serverTicks; 128} 129 130void HifiMoveList::markControlDirty() 131{ 132 mLastClientMove = mLastMoveAck; 133 134 // save state for future update 135 GameBase *obj = mConnection->getControlObject(); 136 AssertFatal(obj,"ClientProcessList::markControlDirty: no control object"); 137 obj->setGhostUpdated(true); 138 obj->getTickCache().beginCacheList(); 139 TickCacheEntry * tce = obj->getTickCache().incCacheList(); 140 BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); 141 obj->writePacketData( mConnection, &bs ); 142} 143 144void HifiMoveList::resetMoveList() 145{ 146 mMoveVec.clear(); 147 mLastMoveAck = 0; 148 mLastClientMove = 0; 149 mFirstMoveIndex = 0; 150 mLastSentMove = 0; 151} 152 153U32 HifiMoveList::getMoves(Move** movePtr,U32* numMoves) 154{ 155 if (mConnection->isConnectionToServer()) 156 // give back moves starting at the last client move... 157 return Parent::getMoves(movePtr,numMoves); 158 159 if (mSuppressMove || mMoveVec.size()==0) 160 { 161 *numMoves=0; 162 *movePtr=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>; 163 } 164 else 165 { 166 *numMoves=1; 167 *movePtr=<a href="/coding/class/classmovelist/#classmovelist_1aaf5b973eb5c053cd4fd08feca2a3974d">mMoveVec</a>.begin(); 168 } 169 170 return *numMoves; 171} 172 173void HifiMoveList::advanceMove() 174{ 175 S32 numMoves = mMoveVec.size(); 176 mAvgMoveQueueSize *= (1.0f-mSmoothMoveAvg); 177 mAvgMoveQueueSize += mSmoothMoveAvg * F32(numMoves); 178 179#ifdef TORQUE_DEBUG_NET_MOVES 180 Con::printf("moves remaining: %i, running avg: %f",numMoves,mAvgMoveQueueSize); 181#endif 182 183 if (mAvgMoveQueueSize<mTargetMoveListSize-mMoveListSizeSlack && numMoves<mTargetMoveListSize && numMoves) 184 { 185 numMoves=0; 186 mAvgMoveQueueSize = (F32)getMax(S32(mAvgMoveQueueSize + mMoveListSizeSlack + 0.5f),numMoves); 187 188#ifdef TORQUE_DEBUG_NET_MOVES 189 Con::printf("too few moves on server, padding with null move"); 190#endif 191 } 192 if (numMoves) 193 numMoves=1; 194 195 if ( mMoveVec.size()>mMaxMoveListSize || (mAvgMoveQueueSize>mTargetMoveListSize+mMoveListSizeSlack && mMoveVec.size()>mTargetMoveListSize) ) 196 { 197 U32 drop = mMoveVec.size()-mTargetMoveListSize; 198 clearMoves(drop); 199 mAvgMoveQueueSize = (F32)mTargetMoveListSize; 200 201#ifdef TORQUE_DEBUG_NET_MOVES 202 Con::printf("too many moves on server, dropping moves (%i)",drop); 203#endif 204 } 205 206 mSuppressMove = numMoves == 0; 207 208 // now clear move 209 if (areMovesPending()) 210 clearMoves(1); 211} 212 213void HifiMoveList::clientWriteMovePacket(BitStream *bstream) 214{ 215 if (!serverTicksInitialized()) 216 resetMoveList(); 217 218 AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index."); 219 220 // enforce limit on number of moves sent 221 if (mLastSentMove<mFirstMoveIndex) 222 mLastSentMove=<a href="/coding/class/classmovelist/#classmovelist_1aba3d83664ed2b543f3873f1a14915ed5">mFirstMoveIndex</a>; 223 U32 count = mLastSentMove-mFirstMoveIndex; 224 225 Move * move = mMoveVec.address(); 226 U32 start = mLastMoveAck; 227 U32 offset; 228 for(offset = 0; offset < count; offset++) 229 if(move[offset].sendCount < MAX_MOVE_PACKET_SENDS) 230 break; 231 if(offset == count && count != 0) 232 offset--; 233 234 start += offset; 235 count -= offset; 236 237 if (count > MaxMoveCount) 238 count = MaxMoveCount; 239 bstream->writeInt(start,32); 240 bstream->writeInt(count,MoveCountBits); 241 Move * prevMove = NULL; 242 for (int i = 0; i < count; i++) 243 { 244 move[offset + i].sendCount++; 245 move[offset + i].pack(bstream,prevMove); 246 bstream->writeInt(move[offset + i].checksum,Move::ChecksumBits); 247 prevMove = &move[offset+i]; 248 } 249} 250 251void HifiMoveList::serverReadMovePacket(BitStream *bstream) 252{ 253 // Server side packet read. 254 U32 start = bstream->readInt(32); 255 U32 count = bstream->readInt(MoveCountBits); 256 257 Move * prevMove = NULL; 258 Move prevMoveHolder; 259 260 // Skip forward (must be starting up), or over the moves 261 // we already have. 262 int skip = mLastMoveAck - start; 263 if (skip < 0) 264 { 265 mLastMoveAck = start; 266 } 267 else 268 { 269 if (skip > count) 270 skip = count; 271 for (int i = 0; i < skip; i++) 272 { 273 prevMoveHolder.unpack(bstream,prevMove); 274 prevMoveHolder.checksum = bstream->readInt(Move::ChecksumBits); 275 prevMove = &prevMoveHolder; 276 S32 idx = mMoveVec.size()-skip+i; 277 if (idx>=0) 278 { 279#ifdef TORQUE_DEBUG_NET_MOVES 280 if (mMoveVec[idx].checksum != prevMoveHolder.checksum) 281 Con::printf("updated checksum on move %i from %i to %i",mMoveVec[idx].id,mMoveVec[idx].checksum,prevMoveHolder.checksum); 282#endif 283 mMoveVec[idx].checksum = prevMoveHolder.checksum; 284 } 285 } 286 start += skip; 287 count = count - skip; 288 } 289 290 // Put the rest on the move list. 291 int index = mMoveVec.size(); 292 mMoveVec.increment(count); 293 while (index < mMoveVec.size()) 294 { 295 mMoveVec[index].unpack(bstream,prevMove); 296 mMoveVec[index].checksum = bstream->readInt(Move::ChecksumBits); 297 prevMove = &mMoveVec[index]; 298 mMoveVec[index].id = start++; 299 index ++; 300 } 301 302 mLastMoveAck += count; 303 304 if (mMoveVec.size()>mMaxMoveListSize) 305 { 306 U32 drop = mMoveVec.size()-mTargetMoveListSize; 307 clearMoves(drop); 308 mAvgMoveQueueSize = (F32)mTargetMoveListSize; 309 310#ifdef TORQUE_DEBUG_NET_MOVES 311 Con::printf("too many moves on server, dropping moves (%i)",drop); 312#endif 313 } 314} 315 316void HifiMoveList::serverWriteMovePacket(BitStream * bstream) 317{ 318#ifdef TORQUE_DEBUG_NET_MOVES 319 Con::printf("ack %i minus %i",mLastMoveAck,mMoveVec.size()); 320#endif 321 322 // acknowledge only those moves that have been ticked 323 bstream->writeInt(mLastMoveAck - mMoveVec.size(),32); 324 325 // send over the current tick count on the server... 326 bstream->writeInt(ServerProcessList::get()->getTotalTicks() & TotalTicksMask, TotalTicksBits); 327} 328 329void HifiMoveList::clientReadMovePacket(BitStream * bstream) 330{ 331 if (!serverTicksInitialized()) 332 resetMoveList(); 333 334#ifdef TORQUE_DEBUG_NET_MOVES 335 Con::printf("pre move ack: %i", mLastMoveAck); 336#endif 337 338 mLastMoveAck = bstream->readInt(32); 339 340#ifdef TORQUE_DEBUG_NET_MOVES 341 Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove); 342#endif 343 344 // This is how many times we've ticked since last ack -- before adjustments below 345 S32 ourTicks = mLastMoveAck - mFirstMoveIndex; 346 347 if (mLastMoveAck < mFirstMoveIndex) 348 mLastMoveAck = mFirstMoveIndex; 349 350 if(mLastMoveAck > mLastClientMove) 351 { 352 ourTicks -= mLastMoveAck-mLastClientMove; 353 mLastClientMove = mLastMoveAck; 354 } 355 while(mFirstMoveIndex < mLastMoveAck) 356 { 357 if (mMoveVec.size()) 358 { 359 mMoveVec.pop_front(); 360 mFirstMoveIndex++; 361 } 362 else 363 { 364 AssertWarn(1, "Popping off too many moves!"); 365 mFirstMoveIndex = mLastMoveAck; 366 } 367 } 368 369 // get server ticks using total number of ticks on server to date... 370 U32 serverTickNum = bstream->readInt(TotalTicksBits); 371 S32 serverTicks = getServerTicks(serverTickNum); 372 S32 tickDiff = serverTicks - ourTicks; 373 374#ifdef TORQUE_DEBUG_NET_MOVES 375 Con::printf("server ticks: %i, client ticks: %i, diff: %i%s", serverTicks, ourTicks, tickDiff, !tickDiff ? "" : " (ticks mis-match)"); 376#endif 377 378 379 // Apply the first (of two) client-side synchronization mechanisms. Key is that 380 // we need to both synchronize client/server move streams (so first move in list is made 381 // at same "time" on both client and server) and maintain the "time" at which the most 382 // recent move was made on the server. In both cases, "time" is the number of ticks 383 // it took to get to that move. 384 updateClientServerTickDiff(tickDiff); 385 386 // Apply the second (and final) client-side synchronization mechanism. The tickDiff adjustments above 387 // make sure time is preserved on client. But that assumes that a future (or previous) update will adjust 388 // time in the other direction, so that we don't get too far behind or ahead of the server. The updateMoveSync 389 // mechanism tracks us over time to make sure we eventually return to be in sync, and makes adjustments 390 // if we don't after a certain time period (number of updates). Unlike the tickDiff mechanism, when 391 // the updateMoveSync acts time is not preserved on the client. 392 HifiClientProcessList * processList = dynamic_cast<HifiClientProcessList*>(ClientProcessList::get()); 393 if (processList) 394 { 395 processList->updateMoveSync(mLastSentMove-mLastClientMove); 396 397 // set catchup parameters... 398 U32 totalCatchup = mLastClientMove - mFirstMoveIndex; 399 400 processList->ageTickCache(ourTicks + (tickDiff>0 ? tickDiff : 0), totalCatchup+1); 401 processList->forceHifiReset(tickDiff!=0); 402 processList->setCatchup(totalCatchup); 403 } 404} 405 406void HifiMoveList::ghostPreRead(NetObject * nobj, bool newGhost) 407{ 408 GameBase* obj = dynamic_cast<GameBase*>(nobj); 409 if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) && !newGhost ) 410 { 411 // set next cache entry to start 412 obj->getTickCache().beginCacheList(); 413 414 // reset to old state because we are about to unpack (and then tick forward) 415 TickCacheEntry * tce = obj->getTickCache().incCacheList(false); 416 if (tce) 417 { 418 BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); 419 obj->readPacketData(mConnection, &bs); 420 } 421 } 422} 423 424void HifiMoveList::ghostReadExtra(NetObject * nobj, BitStream * bstream, bool newGhost) 425{ 426 // Receive additional per ghost information. 427 // Get pending moves for ghosts that have them and add the moves to 428 // the tick cache. 429 GameBase* obj = dynamic_cast<GameBase*>(nobj); 430 if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) ) 431 { 432 // mark ghost so that it updates correctly 433 obj->setGhostUpdated(true); 434 obj->setNewGhost(newGhost); 435 436 // set next cache entry to start 437 obj->getTickCache().beginCacheList(); 438 439 // save state for future update 440 TickCacheEntry * tce = obj->getTickCache().incCacheList(); 441 BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); 442 obj->writePacketData(mConnection, &bs); 443 } 444} 445