/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Oliver Scheuss * Co-authors: * ... * */ // // C++ Implementation: GameStateManager // // Description: // // // Author: Oliver Scheuss, (C) 2007 // // Copyright: See COPYING file that comes with this distribution // // #include "GamestateManager.h" #include #include #include "packet/Acknowledgement.h" #include "packet/Gamestate.h" #include "synchronisable/NetworkCallbackManager.h" #include "core/ThreadPool.h" #include "core/command/Executor.h" #include "core/GameMode.h" #include "util/Output.h" #include "util/Clock.h" #include "util/OrxAssert.h" namespace orxonox { GamestateManager::GamestateManager() : currentGamestate_(nullptr), id_(0) { } GamestateManager::~GamestateManager() { if(this->currentGamestate_) { delete this->currentGamestate_; this->currentGamestate_ = nullptr; } for(const auto& gsPair : this->gamestateQueue) { delete gsPair.second; } for(const auto& peerPair : this->peerMap_) { for(const auto& gsPair : peerPair.second.gamestates) { delete gsPair.second; } } } bool GamestateManager::update() { return this->getSnapshot(); } bool GamestateManager::addGamestate(packet::Gamestate *gs, unsigned int clientID) { assert(gs); // Search the queue for a gamestate for the client std::map::iterator it = this->gamestateQueue.find(clientID); if(it != this->gamestateQueue.end()) { // delete obsolete gamestate delete it->second; } // update the client's gamestate this->gamestateQueue[clientID] = gs; return true; } /** * Process the queued gamestates. */ bool GamestateManager::processGamestates() { // Queue is empty, nothing to do if(this->gamestateQueue.empty()) { return true; } // now push only the most recent gamestates we received (ignore obsolete ones) for(const auto& gsPair : this->gamestateQueue) { OrxVerify(this->processGamestate(gsPair.second), "ERROR: could not process Gamestate"); this->sendAck(gsPair.second->getID(), gsPair.second->getPeerID()); delete gsPair.second; } // now clear the queue this->gamestateQueue.clear(); //and call all queued callbacks NetworkCallbackManager::callCallbacks(); return true; } /** * Send Acknowledgement packet. * @param gamestateId The gamestate ID we want to acknowledge * @param peerID The ID of the peer we want to send the Acknowledgement to */ bool GamestateManager::sendAck(unsigned int gamestateID, uint32_t peerID) { assert(gamestateID != ACKID_NACK); packet::Acknowledgement *ack = new packet::Acknowledgement(gamestateID, peerID); if(!this->sendPacket(ack)) { orxout(internal_warning, context::network) << "could not ack gamestate: " << gamestateID << endl; return false; } else { orxout(verbose_more, context::network) << "acked a gamestate: " << gamestateID << endl; return true; } } /** * Update the current gamestate. */ bool GamestateManager::getSnapshot() { // Delete current gamestate if (this->currentGamestate_) { delete this->currentGamestate_; this->currentGamestate_ = nullptr; } uint8_t gsMode; if(GameMode::isMaster()) { gsMode = packet::GAMESTATE_MODE_SERVER; } else { gsMode = packet::GAMESTATE_MODE_CLIENT; } uint32_t newID; if(GameMode::isMaster()) { newID = ++this->id_; } else { assert(this->peerMap_.size() != 0); newID = this->peerMap_[NETWORK_PEER_ID_SERVER].lastReceivedGamestateID; if(newID == GAMESTATEID_INITIAL) { return false; } } // Create a new gamestate this->currentGamestate_ = new packet::Gamestate(); if(!this->currentGamestate_->collectData(newID, gsMode)) { // we have no data to send delete this->currentGamestate_; this->currentGamestate_ = nullptr; return false; } return true; } /** * Return a vector with the gamestates of all peers. */ std::vector GamestateManager::getGamestates() { // Current gamestate is empty if(!this->currentGamestate_){ return std::vector(); } std::vector peerGamestates; for(const auto& mapEntry : this->peerMap_) { if(!mapEntry.second.isSynched) { orxout(verbose_more, context::network) << "Server: not sending gamestate" << endl; continue; } orxout(verbose_more, context::network) << "client id: " << mapEntry.first << endl; orxout(verbose_more, context::network) << "Server: doing gamestate gamestate preparation" << endl; int peerID = mapEntry.first; //get client id unsigned int lastAckedGamestateID = mapEntry.second.lastAckedGamestateID; packet::Gamestate* baseGamestate=nullptr; if(lastAckedGamestateID != GAMESTATEID_INITIAL) { assert(peerMap_.find(peerID)!=peerMap_.end()); std::map::iterator it = peerMap_[peerID].gamestates.find(lastAckedGamestateID); assert(it!=peerMap_[peerID].gamestates.end()); baseGamestate = it->second; } peerGamestates.push_back(nullptr); // insert an empty gamestate* to be changed finishGamestate( peerID, peerGamestates.back(), baseGamestate, currentGamestate_ ); if(peerGamestates.back() == nullptr) { // nothing to send to remove pointer from vector peerGamestates.pop_back(); } } return peerGamestates; } void GamestateManager::finishGamestate(unsigned int peerID, packet::Gamestate*& destgamestate, packet::Gamestate* base, packet::Gamestate* gamestate) { // save the (undiffed) gamestate in the clients gamestate map // choose whether the next gamestate is the first or not // Create a copy of the gamestate packet::Gamestate *gs = new packet::Gamestate(*gamestate); //this is neccessary because the gamestate are being kept (to diff them later on) for each client seperately this->peerMap_[peerID].gamestates[gamestate->getID()] = gs; // Start the clock Clock clock; clock.capture(); if(base) { packet::Gamestate *diffed1 = gs->diffVariables(base); if(diffed1->getDataSize() == 0) { delete diffed1; destgamestate = nullptr; return; } gs = diffed1; } else { gs = new packet::Gamestate(*gs); } // Stop the clock clock.capture(); orxout(verbose_more, context::network) << "diff and compress time: " << clock.getDeltaTime() << endl; gs->setPeerID(peerID); destgamestate = gs; } /** * Acknowledge a received gamestate. * @param gamestateID The ID of the gamestate to be acknowledged * @param peerID The ID of the peer to send the Acknowledgement to */ bool GamestateManager::ackGamestate(unsigned int gamestateID, unsigned int peerID) { // Search for the peer in the peer map std::map::iterator it = this->peerMap_.find(peerID); assert(it != this->peerMap_.end()); unsigned int curid = it->second.lastAckedGamestateID; assert(gamestateID != ACKID_NACK); // The gamestate has already been acknowledged, nothing to do if(gamestateID <= curid && curid != GAMESTATEID_INITIAL) { return true; } orxout(verbose, context::network) << "acking gamestate " << gamestateID << " for peerID: " << peerID << " curid: " << curid << endl; std::map::iterator it2; for (it2 = it->second.gamestates.begin(); it2 != it->second.gamestates.end();) { if(it2->second->getID() < gamestateID) { delete it2->second; it->second.gamestates.erase(it2++); } else { ++it2; } } // update the last acked gamestate it->second.lastAckedGamestateID = gamestateID; return true; } /** * Return the ID of the last received gamestate for a certain peer * @param peerID The ID of the peer\ */ uint32_t GamestateManager::getLastReceivedGamestateID(unsigned int peerID) { if(this->peerMap_.find(peerID) != this->peerMap_.end()) { return this->peerMap_[peerID].lastReceivedGamestateID; } else { return GAMESTATEID_INITIAL; } } /** * Add a peer to the game. * @param peerID The ID of the peer to add. */ void GamestateManager::addPeer(uint32_t peerID) { // Ensure that the peer doesn't already exist. assert(this->peerMap_.find(peerID) == this->peerMap_.end()); // Create the peerInfo struct for the peer this->peerMap_[peerID].peerID = peerID; this->peerMap_[peerID].lastReceivedGamestateID = GAMESTATEID_INITIAL; this->peerMap_[peerID].lastAckedGamestateID = GAMESTATEID_INITIAL; if(GameMode::isMaster()) { this->peerMap_[peerID].isSynched = false; } else { this->peerMap_[peerID].isSynched = true; } } /** * Remove a peer from the game. * @param peerID The ID of the peer to be removed */ void GamestateManager::removePeer(uint32_t peerID) { // Make sure that the peer exists assert(this->peerMap_.find(peerID) != this->peerMap_.end()); for (const auto& mapEntry : this->peerMap_[peerID].gamestates) { delete mapEntry.second; } this->peerMap_.erase(this->peerMap_.find(peerID)); } /** * Process an incoming Gamestate packet. * @param gs Pointer to the incoming Gamestate packet */ bool GamestateManager::processGamestate(packet::Gamestate *gs) { // Decompress if necessary if(gs->isCompressed()) { OrxVerify(gs->decompressData(), "ERROR: could not decompress Gamestate"); } assert(!gs->isDiffed()); uint8_t gsMode; if(GameMode::isMaster()) { gsMode = packet::GAMESTATE_MODE_SERVER; } else { gsMode = packet::GAMESTATE_MODE_CLIENT; } if(gs->spreadData(gsMode)) { this->peerMap_[gs->getPeerID()].lastReceivedGamestateID = gs->getID(); return true; } else { return false; } } }