Torque3D Documentation / _generateds / hifiGameProcess.cpp

hifiGameProcess.cpp

Engine/source/T3D/gameBase/hifi/hifiGameProcess.cpp

More...

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