//----------------------------------------------------------------------------- // Torque Game Engine // Copyright (C) GarageGames.com, Inc. //----------------------------------------------------------------------------- #include "core/dnet.h" #include "game/gameConnection.h" #include "game/gameBase.h" #include "game/shapeBase.h" #include "platform/profiler.h" #include "console/consoleTypes.h" //---------------------------------------------------------------------------- ProcessList gClientProcessList(false); ProcessList gServerProcessList(true); bool ProcessList::mDebugControlSync = false; ProcessList::ProcessList(bool isServer) { mDirty = false; mCurrentTag = 0; mLastTick = 0; mLastTime = 0; mLastDelta = 0; mIsServer = isServer; // Con::addVariable("debugControlSync",TypeBool, &mDebugControlSync); } //---------------------------------------------------------------------------- void ProcessList::orderList() { // GameBase tags are intialized to 0, so current tag // should never be 0. if (!++mCurrentTag) mCurrentTag++; // Install a temporary head node GameBase list; list.plLinkBefore(head.mProcessLink.next); head.plUnlink(); // Reverse topological sort into the orignal head node while (list.mProcessLink.next != &list) { GameBase* ptr = list.mProcessLink.next; ptr->mProcessTag = mCurrentTag; ptr->plUnlink(); if (ptr->mAfterObject) { // Build chain "stack" of dependant objects and patch // it to the end of the current list. while (bool(ptr->mAfterObject) && ptr->mAfterObject->mProcessTag != mCurrentTag) { ptr->mAfterObject->mProcessTag = mCurrentTag; ptr->mAfterObject->plUnlink(); ptr->mAfterObject->plLinkBefore(ptr); ptr = ptr->mAfterObject; } ptr->plJoin(&head); } else ptr->plLinkBefore(&head); } mDirty = false; } //---------------------------------------------------------------------------- bool ProcessList::advanceServerTime(SimTime timeDelta) { PROFILE_START(AdvanceServerTime); if (mDirty) orderList(); SimTime targetTime = mLastTime + timeDelta; SimTime targetTick = targetTime & ~TickMask; SimTime tickCount = (targetTick - mLastTick) >> TickShift; bool ret = mLastTick != targetTick; // Advance all the objects for (; mLastTick != targetTick; mLastTick += TickMs) advanceObjects(); // Credit all the connections with the elapsed ticks. SimGroup *g = Sim::getClientGroup(); for (SimGroup::iterator i = g->begin(); i != g->end(); i++) if (GameConnection *t = dynamic_cast(*i)) t->incMoveCredit(tickCount); mLastTime = targetTime; PROFILE_END(); return ret; } //---------------------------------------------------------------------------- bool ProcessList::advanceClientTime(SimTime timeDelta) { PROFILE_START(AdvanceClientTime); if (mDirty) orderList(); SimTime targetTime = mLastTime + timeDelta; SimTime targetTick = (targetTime + TickMask) & ~TickMask; SimTime tickCount = (targetTick - mLastTick) >> TickShift; // See if the control object has pending moves. GameBase* control = 0; GameConnection* connection = GameConnection::getConnectionToServer(); if (connection) { // If the connection to the server is backlogged // the simulation is frozen. if (connection->isBacklogged()) { mLastTime = targetTime; mLastTick = targetTick; PROFILE_END(); return false; } if (connection->areMovesPending()) control = connection->getControlObject(); } // If we are going to tick, or have moves pending for the // control object, we need to reset everyone back to their // last full tick pos. if (mLastDelta && (tickCount || control)) for (GameBase* obj = head.mProcessLink.next; obj != &head; obj = obj->mProcessLink.next) if (obj->mProcessTick) obj->interpolateTick(0); // Produce new moves and advance all the objects if (tickCount) { for (; mLastTick != targetTick; mLastTick += TickMs) { if(connection) { // process any demo blocks that are NOT moves, and exactly one move // we advance time in the demo stream by a move inserted on // each tick. So before doing the tick processing we advance // the demo stream until a move is ready if(connection->isPlayingBack()) { U32 blockType; do { blockType = connection->getNextBlockType(); bool res = connection->processNextBlock(); // if there are no more blocks, exit out of this function, // as no more client time needs to process right now - we'll // get it all on the next advanceClientTime() if(!res) { PROFILE_END(); return true; } } while(blockType != GameConnection::BlockTypeMove); } connection->collectMove(mLastTick); } advanceObjects(); } } else if (control) { // Sync up the control object with the latest client moves. Move* movePtr; U32 m = 0, numMoves; connection->getMoveList(&movePtr, &numMoves); while (m < numMoves) { control->processTick(&movePtr[m++]); } connection->clearMoves(m); } mLastDelta = (TickMs - (targetTime & TickMask)) & TickMask; F32 dt = mLastDelta / F32(TickMs); for (GameBase* obj = head.mProcessLink.next; obj != &head; obj = obj->mProcessLink.next) if (obj->mProcessTick) obj->interpolateTick(dt); // Inform objects of total elapsed delta so they can advance // client side animations. dt = F32(timeDelta) / 1000; for (GameBase* obj = head.mProcessLink.next; obj != &head; obj = obj->mProcessLink.next) obj->advanceTime(dt); mLastTime = targetTime; PROFILE_END(); return tickCount != 0; } //---------------------------------------------------------------------------- void ProcessList::advanceObjects() { PROFILE_START(AdvanceObjects); // A little link list shuffling is done here to avoid problems // with objects being deleted from within the process method. GameBase list; GameBase* obj; list.plLinkBefore(head.mProcessLink.next); head.plUnlink(); while ((obj = list.mProcessLink.next) != &list) { obj->plUnlink(); obj->plLinkBefore(&head); // Each object is either advanced a single tick, or if it's // being controlled by a client, ticked once for each pending move. if (obj->mTypeMask & GameBaseObjectType) { GameBase *pGB = static_cast(obj); GameConnection* con = pGB->getControllingClient(); if (con && con->getControlObject() == pGB) { Move* movePtr; U32 m, numMoves; con->getMoveList(&movePtr, &numMoves); for (m = 0; m < numMoves && pGB->getControllingClient() == con; ) obj->processTick(&movePtr[m++]); con->clearMoves(m); continue; } } if (obj->mProcessTick) obj->processTick(0); } PROFILE_END(); }