/* 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 "state.h" #include #include "synchronizeable.h" /** * default constructor */ Synchronizeable::Synchronizeable() { this->setClassID(CL_SYNCHRONIZEABLE, "Synchronizeable"); this->owner = -1; this->hostID = SharedNetworkData::getInstance()->getHostID(); this->setIsServer(this->hostID == 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); } /** * 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++ ) 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; } } 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; } else stateFrom = (*it); StateHistoryEntry * stateTo = new StateHistoryEntry(); 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; // 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 == this->hostID && (*it)->checkPermission( PERMISSION_OWNER ) || (*it)->checkPermission( PERMISSION_ALL ) ); if ( ( hasPermission && (*it)->getPriority() >= priorityTH ) || sizeIter == stateFrom->sizeList.end() ) { //(*it)->debug(); n = (*it)->writeToBuf( stateTo->data+i, stateTo->dataLength - i ); stateTo->sizeList.push_back( n ); i += n; } else { for ( int j = 0; j<(*sizeIter); j++ ) { assert( i < stateFrom->dataLength ); stateTo->data[i] = stateFrom->data[i]; i++; } stateTo->sizeList.push_back( (*sizeIter) ); } if ( sizeIter != stateFrom->sizeList.end() ) sizeIter++; } sentStates[userId].push_back( stateTo ); assert( i == neededSize ); //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 ]; //remove old states StateHistory::iterator it = recvStates[userId].begin(); 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 ); } //find state to apply diff to StateHistoryEntry * stateFrom = NULL; it = recvStates[userId].begin(); while ( it != recvStates[userId].end() && (*it)->stateId != fromStateId ) it++; if ( it == recvStates[userId].end() ) { StateHistoryEntry * initialEntry = new StateHistoryEntry(); initialEntry->stateId = fromStateId; initialEntry->dataLength = 0; initialEntry->data = NULL; stateFrom = initialEntry; } 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; std::list changes; for ( SyncVarList::iterator it = syncVarList.begin(); it != syncVarList.end(); it++ ) { if ( (*it)->checkPermission( PERMISSION_SERVER ) && networkStream->isUserServer( userId ) || (*it)->checkPermission( PERMISSION_OWNER ) && this->owner == userId || (*it)->checkPermission( PERMISSION_ALL ) ) { i += (*it)->readFromBuf( stateTo->data + i, stateTo->dataLength - i ); 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 ); i += (*it)->getSizeFromBuf( stateTo->data + i, stateTo->dataLength - i ); } } 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 ) { for ( UserStateHistory::iterator it = sentStates.begin(); it != sentStates.end(); it++ ) { for ( StateHistory::iterator it2 = it->begin(); it2 != it->end(); it++ ) { if ( (*it2)->data ) delete [] (*it2)->data; (*it2)->data = NULL; delete [] *it2; } } sentStates.clear(); for ( UserStateHistory::iterator it = recvStates.begin(); it != recvStates.end(); it++ ) { for ( StateHistory::iterator it2 = it->begin(); it2 != it->end(); it++ ) { if ( (*it2)->data ) delete [] (*it2)->data; (*it2)->data = NULL; delete [] *it2; } } recvStates.clear(); }