/* orxonox - the future of 3D-vertical-scrollers Copyright (C) 2004 orx 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, or (at your option) any later version. ### File Specific: main-programmer: Silvan Nellen co-programmer: Benjamin Wuest */ #define DEBUG_MODULE_NETWORK #include "shared_network_data.h" #include "network_stream.h" #include "netdefs.h" #include "network_log.h" #include "network_game_manager.h" #include "state.h" #include #include "synchronizeable.h" /** * default constructor */ Synchronizeable::Synchronizeable() { this->setClassID(CL_SYNCHRONIZEABLE, "Synchronizeable"); this->owner = 0; this->setIsServer(SharedNetworkData::getInstance()->getHostID() == 0); this->uniqueID = NET_UID_UNASSIGNED; this->networkStream = NULL; this->bSynchronize = false; if( State::isOnline()) { NetworkStream* nd = SharedNetworkData::getInstance()->getDefaultSyncStream(); assert(nd != NULL); nd->connectSynchronizeable(*this); this->setUniqueID(SharedNetworkData::getInstance()->getNewUniqueID()); } /* make sure loadClassId is first synced var because this is read by networkStream */ assert( syncVarList.size() == 0 ); mLeafClassId = this->registerVarId( new SynchronizeableInt( (int*)&this->getLeafClassID(), (int*)&this->getLeafClassID(), "leafClassId" ) ); this->registerVar( new SynchronizeableInt( &this->owner, &this->owner, "owner" ) ); this->registerVar( new SynchronizeableString( &this->objectName, &this->objectName, "objectName" ) ); } /** * default destructor deletes all unneded stuff */ Synchronizeable::~Synchronizeable() { if ( this->networkStream ) { this->networkStream->disconnectSynchronizeable(*this); if ( this->isServer() && this->beSynchronized() && this->getUniqueID() > 0 && !this->isA( CL_MESSAGE_MANAGER ) ) NetworkGameManager::getInstance()->removeSynchronizeable( this->getUniqueID() ); } for ( SyncVarList::iterator it = syncVarList.begin(); it != syncVarList.end(); it++ ) { delete *it; } syncVarList.clear(); for ( UserStateHistory::iterator it = recvStates.begin(); it != recvStates.end(); it++ ) { for ( StateHistory::iterator it2 = it->begin(); it2 != it->end(); it2++ ) { if ( (*it2)->data ) { delete [] (*it2)->data; (*it2)->data = NULL; } delete *it2; } } for ( UserStateHistory::iterator it = sentStates.begin(); it != sentStates.end(); it++ ) { for ( StateHistory::iterator it2 = it->begin(); it2 != it->end(); it2++ ) { if ( (*it2)->data ) { delete [] (*it2)->data; (*it2)->data = NULL; } delete *it2; } } } /** * Sets the server flag to a given value * @param isServer: the boolean value which the server flag is to set to */ void Synchronizeable::setIsServer(bool isServer) { if( isServer ) this->state = this->state | STATE_SERVER; else this->state = this->state & (~STATE_SERVER); } /** * Determines if the server flag is set * @return true, if the server flag is true, false else */ bool Synchronizeable::isServer() { return (this->state & STATE_SERVER) >0; } int Synchronizeable::getStateDiff( int userId, byte* data, int maxLength, int stateId, int fromStateId, int priorityTH ) { //make sure this user has his history if ( sentStates.size() <= userId ) sentStates.resize( userId+1 ); //calculate needed memory int neededSize = 0; for ( SyncVarList::iterator it = syncVarList.begin(); it != syncVarList.end(); it++ ) { //PRINTF(0)("SIZE = %d %s\n", (*it)->getSize(), (*it)->getName().c_str()); neededSize += (*it)->getSize(); } if ( !( neededSize <= maxLength ) ) { PRINTF(0)( "%d > %d\n", neededSize, maxLength ); assert(false); } //remove older states from history than fromStateId StateHistory::iterator it = sentStates[userId].begin(); while ( it != sentStates[userId].end() && (*it)->stateId < fromStateId ) it++; if ( it != sentStates[userId].begin() ) { for ( StateHistory::iterator it2 = sentStates[userId].begin(); it2 != it; it2++ ) { if ( (*it2)->data != NULL ) { delete [] (*it2)->data; (*it2)->data = NULL; } delete *it2; } sentStates[userId].erase( sentStates[userId].begin(), it ); } //find state to create diff from StateHistoryEntry * stateFrom = NULL; it = sentStates[userId].begin(); while ( it != sentStates[userId].end() && (*it)->stateId != fromStateId ) it++; // if ( getLeafClassID() == CL_SPACE_SHIP ) // { // PRINTF(0)("getStateDiff:SpaceShip from: %d stateId: %d\n", (it == sentStates[userId].end())?-1:fromStateId, stateId); // } if ( it == sentStates[userId].end() ) { StateHistoryEntry * initialEntry = new StateHistoryEntry(); initialEntry->stateId = fromStateId; initialEntry->dataLength = 0; initialEntry->data = NULL; stateFrom = initialEntry; sentStates[userId].push_back( stateFrom ); } else stateFrom = (*it); StateHistoryEntry * stateTo = new StateHistoryEntry; sentStates[userId].push_back( stateTo ); stateTo->stateId = stateId; stateTo->dataLength = neededSize; stateTo->data = new byte[ neededSize ]; std::list::iterator sizeIter = stateFrom->sizeList.begin(); int i = 0; int n; bool hasPermission; bool sizeChanged = false; // now do the actual synchronization: kick all variables to write into a common buffer for ( SyncVarList::iterator it = syncVarList.begin(); it != syncVarList.end(); it++ ) { hasPermission = ( this->isServer() && (*it)->checkPermission( PERMISSION_SERVER ) || this->owner == SharedNetworkData::getInstance()->getHostID() && (*it)->checkPermission( PERMISSION_OWNER ) || this->isServer() && this->owner != userId && (*it)->checkPermission( PERMISSION_OWNER ) || (*it)->checkPermission( PERMISSION_ALL ) ); if ( sizeIter == stateFrom->sizeList.end() || *sizeIter != (*it)->getSize() ) sizeChanged = true; if ( ( hasPermission && (*it)->getPriority() >= priorityTH ) || sizeChanged ) { n = (*it)->writeToBuf( stateTo->data+i, stateTo->dataLength - i ); //NETPRINTF(0)("getvar %s %d\n", (*it)->getName().c_str(), n); stateTo->sizeList.push_back( n ); //(*it)->debug(); i += n; } else { for ( int j = 0; j<(*sizeIter); j++ ) { assert( i < stateFrom->dataLength ); stateTo->data[i] = stateFrom->data[i]; i++; } //NETPRINTF(0)("getvar %s %d\n", (*it)->getName().c_str(), *sizeIter); stateTo->sizeList.push_back( (*sizeIter) ); } if ( sizeIter != stateFrom->sizeList.end() ) sizeIter++; } if ( i != neededSize ) { PRINTF(0)("strange error: (%s) %d != %d\n", this->getClassName(), i, neededSize); assert(false); } //write diff to data for ( i = 0; idataLength ) data[i] = stateTo->data[i] - stateFrom->data[i]; else data[i] = stateTo->data[i]; } return neededSize; } /** * sets a new state out of a diff created on another host * @param userId hostId of user who send me that diff * @param data pointer to diff * @param length length of diff * @param stateId id of current state * @param fromStateId id of the base state id * @return number bytes read * @todo check for permissions */ int Synchronizeable::setStateDiff( int userId, byte* data, int length, int stateId, int fromStateId ) { //make sure this user has his history if ( recvStates.size() <= userId ) recvStates.resize( userId+1 ); //create new state StateHistoryEntry * stateTo = new StateHistoryEntry(); stateTo->stateId = stateId; stateTo->dataLength = length; stateTo->data = new byte[ length ]; //find state to apply diff to StateHistoryEntry * stateFrom = NULL; StateHistory::iterator it = recvStates[userId].begin(); while ( it != recvStates[userId].end() && (*it)->stateId != fromStateId ) it++; // if ( getLeafClassID() == CL_SPACE_SHIP ) // { // PRINTF(0)("setStateDiff:SpaceShip from: %d stateId: %d\n", (it == recvStates[userId].end())?-1:fromStateId, stateId); // } if ( it == recvStates[userId].end() ) { StateHistoryEntry * initialEntry = new StateHistoryEntry(); initialEntry->stateId = fromStateId; initialEntry->dataLength = 0; initialEntry->data = NULL; stateFrom = initialEntry; recvStates[userId].push_back( stateFrom ); } else stateFrom = (*it); //apply diff for ( int i = 0; idataLength ) stateTo->data[i] = stateFrom->data[i] + data[i]; else stateTo->data[i] = data[i]; } //add state to state history recvStates[userId].push_back( stateTo ); int i = 0; int n = 0; std::list changes; for ( SyncVarList::iterator it = syncVarList.begin(); it != syncVarList.end(); it++ ) { if ( (*it)->checkPermission( PERMISSION_SERVER ) && networkStream->isUserMasterServer( userId ) || (*it)->checkPermission( PERMISSION_OWNER ) && this->owner == userId || networkStream->isUserMasterServer( userId ) && this->owner != SharedNetworkData::getInstance()->getHostID() && (*it)->checkPermission( PERMISSION_OWNER ) || (*it)->checkPermission( PERMISSION_ALL ) ) { n = (*it)->readFromBuf( stateTo->data + i, stateTo->dataLength - i ); i += n; //NETPRINTF(0)("%s::setvar %s %d\n", getClassName(), (*it)->getName().c_str(), n); //(*it)->debug(); if ( (*it)->getHasChanged() ) { changes.push_back( (*it)->getVarId() ); } } else { // PRINTF(0)("DONT SET VAR BECAUSE OF PERMISSION: %s %d %d %d %d %d %d\n", (*it)->getName().c_str(), (*it)->checkPermission( PERMISSION_SERVER ), (*it)->checkPermission( PERMISSION_OWNER ), (*it)->checkPermission( PERMISSION_ALL ), networkStream->isUserServer( userId ), this->owner, userId ); n = (*it)->getSizeFromBuf( stateTo->data + i, stateTo->dataLength - i ); //NETPRINTF(0)("%s::setvar %s %d\n", getClassName(), (*it)->getName().c_str(), n); //(*it)->debug(); i += n; } } this->varChangeHandler( changes ); return i; } /** * override this function to be notified on change * of your registred variables. * @param id id's which have changed */ void Synchronizeable::varChangeHandler( std::list & id ) { } /** * registers a varable to be synchronized over network * @param var see src/lib/network/synchronizeable_var/ for available classes */ void Synchronizeable::registerVar( SynchronizeableVar * var ) { //PRINTF(0)("ADDING VAR: %s\n", var->getName().c_str()); syncVarList.push_back( var ); } /** * registers a varable to be synchronized over network * return value is passed to varChangeHandler on change * @param var see src/lib/network/synchronizeable_var/ for available classes * @return handle passed to varChangeHandler on changes */ int Synchronizeable::registerVarId( SynchronizeableVar * var ) { //PRINTF(0)("ADDING VAR: %s\n", var->getName().c_str()); syncVarList.push_back( var ); var->setWatched( true ); var->setVarId( syncVarList.size()-1 ); return syncVarList.size()-1; } /** * removed user's states from memory * @param userId user to clean */ void Synchronizeable::cleanUpUser( int userId ) { if ( recvStates.size() > userId ) { for ( std::list::iterator it = recvStates[userId].begin(); it != recvStates[userId].end(); it++ ) { if ( (*it)->data ) { delete [] (*it)->data; (*it)->data = NULL; } delete *it; } recvStates[userId].clear(); } if ( sentStates.size() > userId ) { for ( std::list::iterator it = sentStates[userId].begin(); it != sentStates[userId].end(); it++ ) { if ( (*it)->data ) { delete [] (*it)->data; (*it)->data = NULL; } delete *it; } sentStates[userId].clear(); } } /** * this function is called after recieving a state. * @param userId * @param stateId * @param fromStateId */ void Synchronizeable::handleRecvState( int userId, int stateId, int fromStateId ) { //make sure this user has his history if ( recvStates.size() <= userId ) recvStates.resize( userId+1 ); //remove old states StateHistory::iterator it = recvStates[userId].begin(); #if 0 while ( it != recvStates[userId].end() && (*it)->stateId < fromStateId ) it++; if ( it != recvStates[userId].begin() ) { for ( StateHistory::iterator it2 = recvStates[userId].begin(); it2 != it; it2++ ) { if ( (*it2)->data != NULL ) { delete [] (*it2)->data; (*it2)->data = NULL; } } recvStates[userId].erase( recvStates[userId].begin(), it ); } #endif for ( it = recvStates[userId].begin(); it != recvStates[userId].end(); ) { if ( (*it)->stateId < fromStateId ) { StateHistory::iterator delIt = it; it ++; if ( (*delIt)->data ) { delete [] (*delIt)->data; (*delIt)->data = NULL; } delete *delIt; recvStates[userId].erase( delIt ); continue; } it++; } StateHistory::iterator fromState = recvStates[userId].end(); StateHistory::iterator toState = recvStates[userId].end(); for ( it = recvStates[userId].begin(); it != recvStates[userId].end(); it++ ) { if ( (*it)->stateId == stateId ) toState = it; if ( (*it)->stateId == fromStateId ) fromState = it; if ( fromState != recvStates[userId].end() && toState != recvStates[userId].end() ) break; } // setStateDiff was not called and i know fromStateId if ( fromState != recvStates[userId].end() && toState == recvStates[userId].end() ) { StateHistoryEntry * entry = new StateHistoryEntry; entry->dataLength = (*fromState)->dataLength; if ( entry->dataLength > 0 ) { entry->data = new byte[entry->dataLength]; assert( (*fromState)->data ); memcpy( entry->data, (*fromState)->data, entry->dataLength ); } else entry->data = NULL; entry->sizeList = (*fromState)->sizeList; entry->stateId = stateId; recvStates[userId].push_back(entry); } } /** * this function is called after sending a state * @param userId * @param stateId * @param fromStateId */ void Synchronizeable::handleSentState( int userId, int stateId, int fromStateId ) { //make sure this user has his history if ( sentStates.size() <= userId ) sentStates.resize( userId+1 ); //remove old states StateHistory::iterator it = sentStates[userId].begin(); for ( it = sentStates[userId].begin(); it != sentStates[userId].end(); ) { if ( (*it)->stateId < fromStateId ) { StateHistory::iterator delIt = it; it ++; if ( (*delIt)->data ) { delete [] (*delIt)->data; (*delIt)->data = NULL; } delete *delIt; sentStates[userId].erase( delIt ); continue; } it++; } StateHistory::iterator fromState = sentStates[userId].end(); StateHistory::iterator toState = sentStates[userId].end(); for ( it = sentStates[userId].begin(); it != sentStates[userId].end(); it++ ) { if ( (*it)->stateId == stateId ) toState = it; if ( (*it)->stateId == fromStateId ) fromState = it; if ( fromState != sentStates[userId].end() && toState != sentStates[userId].end() ) break; } // getStateDiff was not called and i know fromStateId if ( fromState != sentStates[userId].end() && toState == sentStates[userId].end() ) { StateHistoryEntry * entry = new StateHistoryEntry; entry->dataLength = (*fromState)->dataLength; if ( entry->dataLength > 0 ) { entry->data = new byte[entry->dataLength]; assert( (*fromState)->data ); memcpy( entry->data, (*fromState)->data, entry->dataLength ); } else entry->data = NULL; entry->sizeList = (*fromState)->sizeList; entry->stateId = stateId; sentStates[userId].push_back(entry); } }