hifiGameProcess.cpp
Engine/source/T3D/gameBase/hifi/hifiGameProcess.cpp
Public Variables
Detailed Description
Public Variables
F32 gMaxHiFiVelSq
MODULE_END
MODULE_INIT
MODULE_SHUTDOWN
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 "platform/platform.h" 25#include "T3D/gameBase/hifi/hifiGameProcess.h" 26 27#include "platform/profiler.h" 28#include "core/frameAllocator.h" 29#include "core/stream/bitStream.h" 30#include "math/mathUtils.h" 31#include "T3D/gameBase/hifi/hifiMoveList.h" 32#include "T3D/gameBase/gameConnection.h" 33#include "T3D/gameFunctions.h" 34 35 36MODULE_BEGIN( ProcessList ) 37 38 MODULE_INIT 39 { 40 HifiServerProcessList::init(); 41 HifiClientProcessList::init(); 42 } 43 44 MODULE_SHUTDOWN 45 { 46 HifiServerProcessList::shutdown(); 47 HifiClientProcessList::shutdown(); 48 } 49 50MODULE_END; 51 52void HifiServerProcessList::init() 53{ 54 smServerProcessList = new HifiServerProcessList(); 55} 56 57void HifiServerProcessList::shutdown() 58{ 59 delete smServerProcessList; 60} 61 62void HifiClientProcessList::init() 63{ 64 smClientProcessList = new HifiClientProcessList(); 65} 66 67void HifiClientProcessList::shutdown() 68{ 69 delete smClientProcessList; 70} 71 72//---------------------------------------------------------------------------- 73 74F32 gMaxHiFiVelSq = 100 * 100; 75 76namespace 77{ 78 inline GameBase * GetGameBase(ProcessObject * obj) 79 { 80 return static_cast<GameBase*>(obj); 81 } 82 83 // local work class 84 struct GameBaseListNode 85 { 86 GameBaseListNode() 87 { 88 mPrev=this; 89 mNext=this; 90 mObject=<a href="/coding/file/types_8lint_8h/#types_8lint_8h_1a070d2ce7b6bb7e5c05602aa8c308d0c4">NULL</a>; 91 } 92 93 GameBaseListNode * mPrev; 94 GameBaseListNode * mNext; 95 GameBase * mObject; 96 97 void linkBefore(GameBaseListNode * obj) 98 { 99 // Link this before obj 100 mNext = obj; 101 mPrev = obj->mPrev; 102 obj->mPrev = this; 103 mPrev->mNext = this; 104 } 105 }; 106 107 // Structure used for synchronizing move lists on client/server 108 struct MoveSync 109 { 110 enum { ActionCount = 4 }; 111 112 S32 moveDiff; 113 S32 moveDiffSteadyCount; 114 S32 moveDiffSameSignCount; 115 116 bool doAction() { return moveDiffSteadyCount>=ActionCount || moveDiffSameSignCount>=4*ActionCount; } 117 void reset() { moveDiff=0; moveDiffSteadyCount=0; moveDiffSameSignCount=0; } 118 void update(S32 diff); 119 } moveSync; 120 121 void MoveSync::update(S32 diff) 122 { 123 if (diff && diff==moveDiff) 124 { 125 moveDiffSteadyCount++; 126 moveDiffSameSignCount++; 127 } 128 else if (diff*moveDiff>0) 129 { 130 moveDiffSteadyCount = 0; 131 moveDiffSameSignCount++; 132 } 133 else 134 reset(); 135 moveDiff = diff; 136 } 137 138} // namespace 139 140//-------------------------------------------------------------------------- 141// HifiClientProcessList 142//-------------------------------------------------------------------------- 143 144HifiClientProcessList::HifiClientProcessList() 145{ 146 mSkipAdvanceObjectsMs = 0; 147 mForceHifiReset = false; 148 mCatchup = 0; 149} 150 151bool HifiClientProcessList::advanceTime( SimTime timeDelta ) 152{ 153 PROFILE_SCOPE( AdvanceClientTime ); 154 155 if ( mSkipAdvanceObjectsMs && timeDelta > mSkipAdvanceObjectsMs ) 156 { 157 timeDelta -= mSkipAdvanceObjectsMs; 158 advanceTime( mSkipAdvanceObjectsMs ); 159 AssertFatal( !mSkipAdvanceObjectsMs, "mSkipAdvanceObjectsMs must always be positive." ); 160 } 161 162 if ( doBacklogged( timeDelta ) ) 163 return false; 164 165 // remember interpolation value because we might need to set it back 166 F32 oldLastDelta = mLastDelta; 167 168 bool ret = Parent::advanceTime( timeDelta ); 169 170 if ( !mSkipAdvanceObjectsMs ) 171 { 172 AssertFatal( mLastDelta >= 0.0f && mLastDelta <= 1.0f, "mLastDelta must always be zero to one." ); 173 for ( ProcessObject *pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next ) 174 { 175 if ( pobj->isTicking() ) 176 pobj->interpolateTick( mLastDelta ); 177 } 178 179 // Inform objects of total elapsed delta so they can advance 180 // client side animations. 181 F32 dt = F32( timeDelta ) / 1000; 182 for ( ProcessObject *pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) 183 { 184 pobj->advanceTime( dt ); 185 } 186 } 187 else 188 { 189 mSkipAdvanceObjectsMs -= timeDelta; 190 mLastDelta = oldLastDelta; 191 } 192 193 return ret; 194} 195 196void HifiClientProcessList::onAdvanceObjects() 197{ 198 GameConnection* connection = GameConnection::getConnectionToServer(); 199 if(connection) 200 { 201 // process any demo blocks that are NOT moves, and exactly one move 202 // we advance time in the demo stream by a move inserted on 203 // each tick. So before doing the tick processing we advance 204 // the demo stream until a move is ready 205 if(connection->isPlayingBack()) 206 { 207 U32 blockType; 208 do 209 { 210 blockType = connection->getNextBlockType(); 211 bool res = connection->processNextBlock(); 212 // if there are no more blocks, exit out of this function, 213 // as no more client time needs to process right now - we'll 214 // get it all on the next advanceClientTime() 215 if(!res) 216 return; 217 } 218 while(blockType != GameConnection::BlockTypeMove); 219 } 220 if (!mSkipAdvanceObjectsMs) 221 { 222 connection->mMoveList->collectMove(); 223 advanceObjects(); 224 } 225 connection->mMoveList->onAdvanceObjects(); 226 } 227} 228 229void HifiClientProcessList::onTickObject(ProcessObject * pobj) 230{ 231 // Each object is advanced a single tick 232 // If it's controlled by a client, tick using a move. 233 234 Move *movePtr; 235 U32 numMoves; 236 GameConnection *con = pobj->getControllingClient(); 237 SimObjectPtr<GameBase> obj = getGameBase( pobj ); 238 239 if ( obj && con && con->getControlObject() == obj && con->mMoveList->getMoves( &movePtr, &numMoves) ) 240 { 241#ifdef TORQUE_DEBUG_NET_MOVES 242 U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() ); 243#endif 244 245 obj->processTick( movePtr ); 246 247 if ( bool(obj) && obj->getControllingClient() ) 248 { 249 U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() ); 250 251 // set checksum if not set or check against stored value if set 252 movePtr->checksum = newsum; 253 254#ifdef TORQUE_DEBUG_NET_MOVES 255 Con::printf( "move checksum: %i, (start %i), (move %f %f %f)", 256 movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z ); 257#endif 258 } 259 con->mMoveList->clearMoves( 1 ); 260 } 261 else if ( pobj->isTicking() ) 262 pobj->processTick( 0 ); 263 264 if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) ) 265 { 266 GameConnection * serverConnection = GameConnection::getConnectionToServer(); 267 TickCacheEntry * tce = obj->getTickCache().addCacheEntry(); 268 BitStream bs( tce->packetData, TickCacheEntry::MaxPacketSize ); 269 obj->writePacketData( serverConnection, &bs ); 270 271 Point3F vel = obj->getVelocity(); 272 F32 velSq = mDot( vel, vel ); 273 gMaxHiFiVelSq = getMax( gMaxHiFiVelSq, velSq ); 274 } 275} 276 277void HifiClientProcessList::advanceObjects() 278{ 279#ifdef TORQUE_DEBUG_NET_MOVES 280 Con::printf("Advance client time..."); 281#endif 282 283 // client re-computes this each time objects are advanced 284 gMaxHiFiVelSq = 0; 285 Parent::advanceObjects(); 286 287 // We need to consume a move on the connections whether 288 // there is a control object to consume the move or not, 289 // otherwise client and server can get out of sync move-wise 290 // during startup. If there is a control object, we cleared 291 // a move above. Handle case where no control object here. 292 // Note that we might consume an extra move here and there when 293 // we had a control object in above loop but lost it during tick. 294 // That is no big deal so we don't bother trying to carefully 295 // track it. 296 GameConnection * client = GameConnection::getConnectionToServer(); 297 if (client && client->getControlObject() == NULL) 298 client->mMoveList->clearMoves(1); 299 300#ifdef TORQUE_DEBUG_NET_MOVES 301 Con::printf("---------"); 302#endif 303} 304 305void HifiClientProcessList::ageTickCache(S32 numToAge, S32 len) 306{ 307 for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) 308 { 309 GameBase *obj = getGameBase(pobj); 310 if ( obj && obj->getTypeMask() & GameBaseHiFiObjectType ) 311 obj->getTickCache().ageCache(numToAge,len); 312 } 313} 314 315void HifiClientProcessList::updateMoveSync(S32 moveDiff) 316{ 317 moveSync.update(moveDiff); 318 if (moveSync.doAction() && moveDiff<0) 319 { 320 skipAdvanceObjects(TickMs * -moveDiff); 321 moveSync.reset(); 322 } 323} 324 325void HifiClientProcessList::clientCatchup(GameConnection * connection) 326{ 327#ifdef TORQUE_DEBUG_NET_MOVES 328 Con::printf("client catching up... (%i)%s", mCatchup, mForceHifiReset ? " reset" : ""); 329#endif 330 331 if (connection->getControlObject() && connection->getControlObject()->isGhostUpdated()) 332 // if control object is reset, make sure moves are reset too 333 connection->mMoveList->resetCatchup(); 334 335 const F32 maxVel = mSqrt(gMaxHiFiVelSq) * 1.25f; 336 F32 dt = F32(mCatchup+1) * TickSec; 337 Point3F bigDelta(maxVel*dt,maxVel*dt,maxVel*dt); 338 339 // walk through all process objects looking for ones which were updated 340 // -- during first pass merely collect neighbors which need to be reset and updated in unison 341 ProcessObject * pobj; 342 if (mCatchup && !mForceHifiReset) 343 { 344 for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) 345 { 346 GameBase *obj = getGameBase( pobj ); 347 static SimpleQueryList nearby; 348 nearby.mList.clear(); 349 // check for nearby objects which need to be reset and then caught up 350 // note the funky loop logic -- first time through obj is us, then 351 // we start iterating through nearby list (to look for objects nearby 352 // the nearby objects), which is why index starts at -1 353 // [objects nearby the nearby objects also get added to the nearby list] 354 for (S32 i=-1; obj; obj = ++i<nearby.mList.size() ? (GameBase*)nearby.mList[i] : NULL) 355 { 356 if (obj->isGhostUpdated() && (obj->getTypeMask() & GameBaseHiFiObjectType) && !obj->isNetNearbyAdded()) 357 { 358 Point3F start = obj->getWorldSphere().center; 359 Point3F end = start + 1.1f * dt * obj->getVelocity(); 360 F32 rad = 1.5f * obj->getWorldSphere().radius; 361 362 // find nearby items not updated but are hi fi, mark them as updated (and restore old loc) 363 // check to see if added items have neighbors that need updating 364 Box3F box; 365 Point3F rads(rad,rad,rad); 366 box.minExtents = box.maxExtents = start; 367 box.minExtents -= bigDelta + rads; 368 box.maxExtents += bigDelta + rads; 369 370 // CodeReview - this is left in for MBU, but also so we can deal with the issue later. 371 // add marble blast hack so hifi networking can see hidden objects 372 // (since hidden is under control of hifi networking) 373 // gForceNotHidden = true; 374 375 S32 j = nearby.mList.size(); 376 gClientContainer.findObjects(box, GameBaseHiFiObjectType, SimpleQueryList::insertionCallback, &nearby); 377 378 // CodeReview - this is left in for MBU, but also so we can deal with the issue later. 379 // disable above hack 380 // gForceNotHidden = false; 381 382 // drop anyone not heading toward us or already checked 383 for (; j<nearby.mList.size(); j++) 384 { 385 GameBase * obj2 = (GameBase*)nearby.mList[j]; 386 // if both passive, these guys don't interact with each other 387 bool passive = obj->isHifiPassive() && obj2->isHifiPassive(); 388 if (!obj2->isGhostUpdated() && !passive) 389 { 390 // compare swept spheres of obj and obj2 391 // if collide, reset obj2, setGhostUpdated(true), and continue 392 Point3F end2 = obj2->getWorldSphere().center; 393 Point3F start2 = end2 - 1.1f * dt * obj2->getVelocity(); 394 F32 rad2 = 1.5f * obj->getWorldSphere().radius; 395 if (MathUtils::capsuleCapsuleOverlap(start,end,rad,start2,end2,rad2)) 396 { 397 // better add obj2 398 obj2->getTickCache().beginCacheList(); 399 TickCacheEntry * tce = obj2->getTickCache().incCacheList(); 400 BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); 401 obj2->readPacketData(connection,&bs); 402 obj2->setGhostUpdated(true); 403 404 // continue so we later add the neighbors too 405 continue; 406 } 407 408 } 409 410 // didn't pass above test...so don't add it or nearby objects 411 nearby.mList[j] = nearby.mList.last(); 412 nearby.mList.decrement(); 413 j--; 414 } 415 obj->setNetNearbyAdded(true); 416 } 417 } 418 } 419 } 420 421 // save water mark -- for game base list 422 FrameAllocatorMarker mark; 423 424 // build ordered list of client objects which need to be caught up 425 GameBaseListNode list; 426 for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next) 427 { 428 GameBase *obj = getGameBase( pobj ); 429 //GameBase *obj = dynamic_cast<GameBase*>( pobj ); 430 //GameBase *obj = (GameBase*)pobj; 431 432 // Not a GameBase object so nothing to do. 433 if ( !obj ) 434 continue; 435 436 if (obj->isGhostUpdated() && (obj->getTypeMask() & GameBaseHiFiObjectType)) 437 { 438 // construct process object and add it to the list 439 // hold pointer to our object in mAfterObject 440 GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode)); 441 po->mObject = obj; 442 po->linkBefore(&list); 443 444 // begin iterating through tick list (skip first tick since that is the state we've been reset to) 445 obj->getTickCache().beginCacheList(); 446 obj->getTickCache().incCacheList(); 447 } 448 else if (mForceHifiReset && (obj->getTypeMask() & GameBaseHiFiObjectType)) 449 { 450 // add all hifi objects 451 obj->getTickCache().beginCacheList(); 452 TickCacheEntry * tce = obj->getTickCache().incCacheList(); 453 BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); 454 obj->readPacketData(connection,&bs); 455 obj->setGhostUpdated(true); 456 457 // construct process object and add it to the list 458 // hold pointer to our object in mAfterObject 459 GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode)); 460 po->mObject = obj; 461 po->linkBefore(&list); 462 } 463 else if (obj == connection->getControlObject() && obj->isGhostUpdated()) 464 { 465 // construct process object and add it to the list 466 // hold pointer to our object in mAfterObject 467 // .. but this is not a hi fi object, so don't mess with tick cache 468 GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode)); 469 po->mObject = obj; 470 po->linkBefore(&list); 471 } 472 else if (obj->isGhostUpdated()) 473 { 474 // not hifi but we were updated, so perform net smooth now 475 obj->computeNetSmooth(mLastDelta); 476 } 477 478 // clear out work flags 479 obj->setNetNearbyAdded(false); 480 obj->setGhostUpdated(false); 481 } 482 483 // run through all the moves in the move list so we can play them with our control object 484 Move* movePtr; 485 U32 numMoves; 486 connection->mMoveList->resetClientMoves(); 487 connection->mMoveList->getMoves(&movePtr, &numMoves); 488 AssertFatal(mCatchup<=numMoves,"doh"); 489 490 // tick catchup time 491 for (U32 m=0; m<mCatchup; m++) 492 { 493 for (GameBaseListNode * walk = list.mNext; walk != &list; walk = walk->mNext) 494 { 495 // note that we get object from after object not getGameBase function 496 // this is because we are an on the fly linked list which uses mAfterObject 497 // rather than the linked list embedded in GameBase (clean this up?) 498 GameBase * obj = walk->mObject; 499 500 // it's possible for a non-hifi object to get in here, but 501 // only if it is a control object...make sure we don't do any 502 // of the tick cache stuff if we are not hifi. 503 bool hifi = obj->getTypeMask() & GameBaseHiFiObjectType; 504 TickCacheEntry * tce = hifi ? obj->getTickCache().incCacheList() : NULL; 505 506 // tick object 507 if (obj==connection->getControlObject()) 508 { 509 obj->processTick(movePtr); 510 movePtr->checksum = obj->getPacketDataChecksum(connection); 511 movePtr++; 512 } 513 else 514 { 515 AssertFatal(tce && hifi,"Should not get in here unless a hi fi object!!!"); 516 obj->processTick(tce->move); 517 } 518 519 if (hifi) 520 { 521 BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize); 522 obj->writePacketData(connection,&bs); 523 } 524 } 525 if (connection->getControlObject() == NULL) 526 movePtr++; 527 } 528 connection->mMoveList->clearMoves(mCatchup); 529 530 // Handle network error smoothing here...but only for control object 531 GameBase * control = connection->getControlObject(); 532 if (control && !control->isNewGhost()) 533 { 534 control->computeNetSmooth(mLastDelta); 535 control->setNewGhost(false); 536 } 537 538 if (moveSync.doAction() && moveSync.moveDiff>0) 539 { 540 S32 moveDiff = moveSync.moveDiff; 541#ifdef TORQUE_DEBUG_NET_MOVES 542 Con::printf("client timewarping to catchup %i moves",moveDiff); 543#endif 544 while (moveDiff--) 545 advanceObjects(); 546 moveSync.reset(); 547 } 548 549#ifdef TORQUE_DEBUG_NET_MOVES 550 Con::printf("---------"); 551#endif 552 553 // all caught up 554 mCatchup = 0; 555} 556 557//-------------------------------------------------------------------------- 558// HifiServerProcessList 559//-------------------------------------------------------------------------- 560 561void HifiServerProcessList::onTickObject(ProcessObject * pobj) 562{ 563 // Each object is advanced a single tick 564 // If it's controlled by a client, tick using a move. 565 566 Move *movePtr; 567 U32 numMoves; 568 GameConnection *con = pobj->getControllingClient(); 569 SimObjectPtr<GameBase> obj = getGameBase( pobj ); 570 571 if ( obj && con && con->getControlObject() == obj && con->mMoveList->getMoves( &movePtr, &numMoves ) ) 572 { 573#ifdef TORQUE_DEBUG_NET_MOVES 574 U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() ); 575#endif 576 577 obj->processTick(movePtr); 578 579 if ( bool(obj) && obj->getControllingClient() ) 580 { 581 U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() ); 582 583 // check move checksum 584 if ( movePtr->checksum != newsum ) 585 { 586#ifdef TORQUE_DEBUG_NET_MOVES 587 if ( !obj->mIsAiControlled ) 588 Con::printf( "move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)", 589 movePtr->id, movePtr->checksum, newsum, sum, movePtr->yaw, movePtr->y, movePtr->z ); 590#endif 591 movePtr->checksum = Move::ChecksumMismatch; 592 } 593 else 594 { 595#ifdef TORQUE_DEBUG_NET_MOVES 596 Con::printf( "move %i checksum agree: %i == %i, (start %i), (move %f %f %f)", 597 movePtr->id, movePtr->checksum, newsum, sum, movePtr->yaw, movePtr->y, movePtr->z ); 598#endif 599 } 600 601 // Adding this seems to fix constant corrections, but is it 602 // really a sound fix? 603 con->mMoveList->clearMoves( 1 ); 604 } 605 } 606 else if ( pobj->isTicking() ) 607 pobj->processTick( 0 ); 608} 609