/* 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: Christoph Renner (rennerc@ee.ethz.ch) co-programmer: Patrick Boenzli (patrick@orxonox.ethz.ch) */ #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" #include "converter.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 ( (SharedNetworkData::getInstance()->isMasterServer() /*|| SharedNetworkData::getInstance()->isProxyServerActive()*/ ) && 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; } } } /** * creates a diff image from two states * @param userId: the userid of the user where the image will be sent to * @param data: the binary data array to write to * @param maxLength: maximal length of the data written (length of available space in the array) * @param stateId: the state id that this diff will represent * @param priorityTH: the priority threshold: all syncs below this threshold won't be synchronized * * @todo check for permissions */ 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 ( 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 = false; 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++ ) { // DATA PERMISSIONS // check if this synchronizeable has the permissions to write the data // first check MASTER_SERVER permissions if( SharedNetworkData::getInstance()->isMasterServer() && (*it)->checkPermission( PERMISSION_MASTER_SERVER )) hasPermission = true; // now check PROXY_SERVER permissions else if( SharedNetworkData::getInstance()->isProxyServerActive() && (*it)->checkPermission( PERMISSION_PROXY_SERVER )) hasPermission = true; // now check OWNER permissions else if( this->owner == SharedNetworkData::getInstance()->getHostID() && (*it)->checkPermission( PERMISSION_OWNER )) hasPermission = true; // now check ALL permissions else if( (*it)->checkPermission( PERMISSION_ALL )) hasPermission = true; // SPECIAL: get write permissions if i am master server and i am able to overwrite the client stuff else if( SharedNetworkData::getInstance()->isMasterServer() && this->owner != userId && (*it)->checkPermission( PERMISSION_OWNER )) hasPermission = true; // SPECIAL: get write permissions if i am proxy server and i am able to overwrite the client stuff else if( SharedNetworkData::getInstance()->isProxyServerActive() && this->owner != userId && (*it)->checkPermission( PERMISSION_OWNER )) hasPermission = true; else hasPermission = false; 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); //PRINTF(0)("getvar %s %d\n", (*it)->getName().c_str(), n); stateTo->sizeList.push_back( n ); // this is only for very hardcore debug sessions // (*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->getClassCName(), 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; // search the state from wich the diff is made of StateHistory::iterator it = recvStates[userId].begin(); while ( it != recvStates[userId].end() && (*it)->stateId != fromStateId ) it++; // if this is the first state to receive 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; bool hasPermission = false; // extract the new state for every client for ( SyncVarList::iterator it = syncVarList.begin(); it != syncVarList.end(); it++ ) { // DATA PERMISSIONS // check if this synchronizeable has the permissions to write the data // first check MASTER_SERVER permissions if( this->networkStream->isUserMasterServer( userId ) && (*it)->checkPermission( PERMISSION_MASTER_SERVER )) hasPermission = true; // now check PROXY_SERVER permissions else if( this->networkStream->isUserProxyServerActive( userId ) && (*it)->checkPermission( PERMISSION_PROXY_SERVER )) hasPermission = true; // now check OWNER permissions else if( this->owner == userId && (*it)->checkPermission( PERMISSION_OWNER )) hasPermission = true; // now check ALL permissions else if( (*it)->checkPermission( PERMISSION_ALL )) hasPermission = true; // SPECIAL: get write permissions if im sending to a master server that does not own this sync else if( this->networkStream->isUserMasterServer( userId ) && this->owner != SharedNetworkData::getInstance()->getHostID() && (*it)->checkPermission( PERMISSION_OWNER )) hasPermission = true; // SPECIAL: get write permissions if im sending to a proxy server that does not own this sync else if( this->networkStream->isUserProxyServerActive( userId ) && this->owner != SharedNetworkData::getInstance()->getHostID() && (*it)->checkPermission( PERMISSION_OWNER )) hasPermission = true; else hasPermission = false; // if it has the permission to write do it if( hasPermission) { n = (*it)->readFromBuf( stateTo->data + i, stateTo->dataLength - i ); i += n; //NETPRINTF(0)("%s::setvar %s %d\n", getClassCName(), (*it)->getName().c_str(), n); //PRINTF(0)("%s::setvar %s %d\n", getClassCName(), (*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_MASTER_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", getClassCName(), (*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 ) { 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 ) { 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); } }