// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #ifndef NL_CLIENT_HOST_H #define NL_CLIENT_HOST_H #include "nel/misc/types_nl.h" #include "nel/misc/vector.h" #include "nel/misc/time_nl.h" #include "nel/net/inet_address.h" #include "nel/net/login_cookie.h" #include "fe_types.h" #include "fe_receive_task.h" #include "game_share/action.h" #include "client_entity_id_translator.h" #include "impulse_encoder.h" #include "entity_container.h" #include "game_share/ryzom_entity_id.h" #include "game_share/entity_types.h" #include "game_share/welcome_service_itf.h" #include #include const uint32 FirstClientId = 1; const uint16 InvalidClientId = 0xFFFF; namespace NLNET { class CUdpSock; }; struct TPairState; /** * CClientIdPool */ class CClientIdPool { public: /// Constructor CClientIdPool() { TClientId i; for ( i=0; i<=MaxNbClients; ++i ) { _UsedPool[i] = false; } } /// Get a free Id TClientId getNewClientId() { TClientId i; for ( i=FirstClientId; i<=MaxNbClients; ++i ) { if ( ! _UsedPool[i] ) { _UsedPool[i] = true; return i; } } return InvalidClientId; } /// Release an Id void releaseId( TClientId id ) { _UsedPool[id] = false; } private: bool _UsedPool [MAX_NB_CLIENTS+1]; }; /// Get string for association state const char *associationStateToString( uint8 as ); /** * Client host * \author Olivier Cado * \author Nevrax France * \date 2001 */ class CClientHost { public: /// Constructor CClientHost( const NLNET::CInetAddress& addr, TClientId id ) : Uid (0xFFFFFFFF), InstanceId(0xFFFFFFFF), StartupRole(WS::TUserRole::ur_player), NbFreeEntityItems( 255 ), PrioAmount( 0.0f ), _Address(addr), _ClientId(id), _Id(NLMISC::CEntityId::Unknown), _EntityIndex(), _SendNumber(0), _SendSyncTick(0), _Synchronized(false), _Disconnected(false), _FirstReceiveNumber(0), _ReceiveNumber(0xFFFFFFFF), _ReceiveTime(0), _DatagramLost(0), _DatagramRepeated(0), //SheetId(CLFECOMMON::INVALID_SHEETID), //_ToggleBit(false), //_OutBoxMeter(0), IdTranslator(), ImpulseEncoder(), LastReceivedAck(0xFFFFFFFF), ImpulseMultiPartNumber (0), AuthorizedCharSlot(~0), LastReceivedGameCycle(0), LastSentSync(1), // LastDummy(~0), LastSentDummy(~0), LastSentCounter(0), LastCounterTime(0), // //AvailableImpulseBitsize( "AvailImpulseBitsize", MaxImpulseBitSizes[2] ), NbActionsSentAtCycle(0), QuitId(0) { IdTranslator.setId( id ); ImpulseEncoder.setClientHost( this ); ConnectionState = Synchronize; initClientBandwidth(); } /// Destructor ~CClientHost(); /// Return IP and port const NLNET::CInetAddress& address() { return _Address; } /// Return client Id TClientId clientId() const { return _ClientId; } /// Return the entity index (for access in the entity container) TEntityIndex entityIndex() const { return _EntityIndex; } /// Return the id const NLMISC::CEntityId& eId() const { return _Id; } /// Set the entity index void setEntityIndex( const TEntityIndex& ei ); /// Set the CEntityId void setEId( const NLMISC::CEntityId& assigned_id ); /// Prepare a clean new outbox with current values void setupOutBox( TOutBox& outbox ); /// Prepare a clean system header void setupSystemHeader( TOutBox& outbox, uint8 code); /// Compute host stats void computeHostStats( const TReceivedMessage& msgin, uint32 currentcounter, bool updateAcknowledge ); /// Increment send number and return it uint32 getNextSendNumber() { return ++_SendNumber; } /// R access to last send number uint32 sendNumber() const { return _SendNumber; } /// R/W access to first receive number uint32& firstReceiveNumber() { return _FirstReceiveNumber; } /// R/W access to latest receive number uint32& receiveNumber() { return _ReceiveNumber; } /// R/W access to the toggle bit //bool& toggleBit() { return _ToggleBit; } /// Set receive time now void setReceiveTimeNow(); /// Return receive time NLMISC::TTime receiveTime() const { return _ReceiveTime; } uint32 datagramLost() const { return _DatagramLost; } void resetDatagramLost() { _DatagramLost = 0; } uint32 datagramRepeated() const { return _DatagramRepeated; } /// Setup sync for tick measures with client connection void setFirstSentPacket(uint32 sentPacket, NLMISC::TGameCycle atTick) { #ifdef HALF_FREQUENCY_SENDING_TO_CLIENT _SendSyncTick = atTick - sentPacket*2; #else _SendSyncTick = atTick - sentPacket; #endif } /// Convert ack contained in client packet into tick date NLMISC::TGameCycle getPacketTickDate(uint32 receivedPacketAck) const { return _SendSyncTick+receivedPacketAck; } /// Get sync value for this client; NLMISC::TGameCycle getSync() const { return _SendSyncTick; } void disconnect() { _Disconnected = true; } bool isDisconnected() { return _Disconnected; } // Reset client vision void resetClientVision(); /// Set clienthost to synchronize state void setSynchronizeState() { ConnectionState = Synchronize; } /// Set clienthost to synchronize state void setConnectedState() { ConnectionState = Connected; } /// Set clienthost to synchronize state void setStalledState() { ConnectionState = Stalled; } /// Set clienthost to probe state void setProbeState() { ConnectionState = Probe; LastSentProbe = 0; LastProbeTime = 0; NumConsecutiveProbes = 0; LastReceivedProbe = 0; } /// Set clienthost to ForceSynchronize state (i.e. Synchronize must not be replaced by Connected, as a sync must be sent to the client) void setForceSynchronizeState() { ConnectionState = ForceSynchronize; } /// Initialize the counter/flag void initSendCycle( bool initialState ) { _WhenToSend = initialState; } /// Update the counter/flag void incSendCycle() { #ifdef HALF_FREQUENCY_SENDING_TO_CLIENT _WhenToSend = !_WhenToSend; #endif } /// Return true if the counter/flag state is "to send" for the current cycle bool whenToSend() { #ifdef HALF_FREQUENCY_SENDING_TO_CLIENT return _WhenToSend; #else return true; #endif } /// display nlinfo void displayClientProperties( bool full=true, bool allProps=false, bool sortByDistance=false,NLMISC::CLog *log = NLMISC::InfoLog ) const; /// display nlinfo for one slot void displaySlotProperties( CLFECOMMON::TCLEntityId e, bool full=false, NLMISC::CLog *log = NLMISC::InfoLog ) const; /// display nlinfo (1 line only) void displayShortProps(NLMISC::CLog *log = NLMISC::InfoLog) const; /// Return the cardinal direction from the player to the seen entity const char * getDirection( CEntity *seenEntity, const TEntityIndex& seenEntityIndex ) const; /// Initialize the client bandwidth (calls setClientBandwidth) void initClientBandwidth(); /// Change the client bandwidth (set the nomimal size) void setClientBandwidth( sint32 cbw ) { _MaxOutboxSizeInBit = cbw; _BitBandwidthUsageAvg = _MaxOutboxSizeInBit; _BitImpulsionUsageAvg = MaxImpulseBitSizes[2]; _ImpulsionPrevRemainingActions = 0; } /// Return the current maximum number of bits that can fit in the outbox sint32 getCurrentThrottle() const { return min( (sint32)(_MaxOutboxSizeInBit*2-_BitBandwidthUsageAvg), (sint32)(_MaxOutboxSizeInBit*3/2) ); } /// Update the average bits filled that determine the throttle void updateThrottle( TOutBox& outbox ) { #ifdef NL_DEBUG sint32 prevThrottle = getCurrentThrottle(); #endif // Update the average of bits sent _BitBandwidthUsageAvg = (_BitBandwidthUsageAvg*(_SendNumber-1) + outbox.getPosInBit()) / _SendNumber; #ifdef NL_DEBUG sint32 currThrottle = getCurrentThrottle(); if ( currThrottle != prevThrottle ) nldebug( "NFC: Client %hu, packet %u: throttle %d, %d filled now, %d average (%d kbps), %d nominal", _ClientId, _SendNumber, currThrottle, outbox.getPosInBit(), _BitBandwidthUsageAvg, _BitBandwidthUsageAvg*5/1000, _MaxOutboxSizeInBit ); #endif } /** * Calculate the current maximum number of bits that can fit for impulsions, * and set AvailableImpulsionBitSize in the mirror, using: * - The number of bits filled (possibly exceeding the max when sending forced actions (database)) * - The number of remaining actions not forced */ void setImpulsionThrottle( sint32 currentNbBitsFilled, sint32 nominalBitSize, uint nbRemainingActions ) { if ( !_EntityIndex.isValid() ) return; if ( ((_ImpulsionPrevRemainingActions>12) && (nbRemainingActions > _ImpulsionPrevRemainingActions * 5/4)) || _ImpulsionPrevRemainingActions>100 ) { // The connection does not manage to send all the impulsions that come from the back-end, stop the database impulsions CMirrorPropValue availableImpulseBitsize( TheDataset, _EntityIndex, DSFirstPropertyAvailableImpulseBitSize ); availableImpulseBitsize = 0; #ifdef NL_DEBUG nldebug( "NFC: Client %hu: Blocking the AvailableImpulseBitsize to prevent impulsion congestion", _ClientId ); #endif } else { // The remaining actions number is stable, calculate the available bitsize _BitImpulsionUsageAvg = (_BitImpulsionUsageAvg*(_SendNumber-1) + currentNbBitsFilled) / _SendNumber; sint32 availBitsize; if ( _BitImpulsionUsageAvg < nominalBitSize ) availBitsize = nominalBitSize; else availBitsize = max( (sint32)0, nominalBitSize*2 - _BitImpulsionUsageAvg ); CMirrorPropValue availableImpulseBitsize( TheDataset, _EntityIndex, DSFirstPropertyAvailableImpulseBitSize ); #ifdef NL_DEBUG if ( availBitsize != availableImpulseBitsize ) nldebug( "NFC: Client %hu, packet %u: AvailableImpulseBitsize %u, %d filled now, %d average, %d nominal", _ClientId, _SendNumber, availBitsize, currentNbBitsFilled, _BitImpulsionUsageAvg, nominalBitSize ); #endif availableImpulseBitsize = (uint16)availBitsize; } _ImpulsionPrevRemainingActions = nbRemainingActions; } /// Force the impulsion throttle to 0 to prevent overflooding, when the FS does send any impulsion. void setIdleImpulsionThrottle() { if ( !_EntityIndex.isValid() ) return; CMirrorPropValue availableImpulseBitsize( TheDataset, _EntityIndex, DSFirstPropertyAvailableImpulseBitSize ); availableImpulseBitsize = 0; #ifdef NL_DEBUG nldebug( "NFC: Client %hu: Blocking the AvailableImpulseBitsize to prevent impulsion congestion", _ClientId ); #endif // The FS must not send any impulsion at this time _ImpulsionPrevRemainingActions = 0; } // get Pair state TPairState& getPairState(TCLEntityId e); // get Pair state const TPairState& getPairState(TCLEntityId e) const; /// User identifier TUid Uid; /// User name (put on the NeL Launcher, transmitted by the login system) string UserName; /// User privilege (put on the NeL Launcher, transmitted by the login system) string UserPriv; /// User extended data (put on the NeL Launcher, transmitted by the login system) string UserExtended; /// Language Id string LanguageId; /// Login cookie NLNET::CLoginCookie LoginCookie; /// Startup instance uint32 InstanceId; /// Startup role WS::TUserRole StartupRole; /// Sheet identifier //CLFECOMMON::TSheetId SheetId; /// Number of free entity items uint16 NbFreeEntityItems; // Used to differenciate different multi part impulse uint8 ImpulseMultiPartNumber; // The only character slot that will be granted to connect [0..4], or 0xF (15) for any character uint8 AuthorizedCharSlot; /// States of the client connection enum { Synchronize = 0, Connected, Probe, Stalled, // Disconnect, // Appears to be unused at least for now ServerDown, ForceSynchronize, // prevents Synchronize to be replaced by Connected }; /// The current state of the client connection uint ConnectionState; /// The last received probe; uint32 LastReceivedProbe; /// The number of consecutive probes received lastly uint32 NumConsecutiveProbes; /// The last sent probe; uint32 LastSentProbe; /// The last sent sync uint32 LastSentSync; /// The time the last probe was sent NLMISC::TTime LastProbeTime; /// Stat float PrioAmount; /// Counter check uint32 LastSentCounter; /// Counter time NLMISC::TTime LastCounterTime; /// @name Generic action multipart handling structures //@{ struct CGenericMultiPartTemp { CGenericMultiPartTemp () : NbBlock(0xFFFFFFFF) { } uint32 NbBlock; uint32 NbCurrentBlock; uint32 TempSize; std::vector > Temp; std::vector BlockReceived; void set (CLFECOMMON::CActionGenericMultiPart *agmp, CClientHost *client); }; std::vector GenericMultiPartTemp; //@} /// Quit Id uint32 QuitId; private: /// Client IP and port NLNET::CInetAddress _Address; /// Client Id TClientId _ClientId; /// Entity Id TEntityIndex _EntityIndex; /// CEntityId NLMISC::CEntityId _Id; /// Latest send number uint32 _SendNumber; /// Counter (or flag) determining when to send the prioritized properties to the client bool _WhenToSend; /// First tick on front end, corresponding to the first packet sent NLMISC::TGameCycle _SendSyncTick; bool _Synchronized; bool _Disconnected; /// First receive number uint32 _FirstReceiveNumber; /// Latest receive number uint32 _ReceiveNumber; /// Bit for important actions //bool _ToggleBit; /// Timestamp of latest receiving NLMISC::TTime _ReceiveTime; /// Stat uint32 _DatagramLost; /// Stat uint32 _DatagramRepeated; /// Nominal size sint32 _MaxOutboxSizeInBit; /// Number of bits that can be added to the nominal size (>0), or that must be removed from the nominal size (<0) sint32 _DeltaBitsAllowed; /// Previous number of actions not sent by the impulse encoder uint32 _ImpulsionPrevRemainingActions; /// Average of total bits sent sint32 _BitBandwidthUsageAvg; /// Average of impulsion bits sent sint32 _BitImpulsionUsageAvg; public: // Client <- frontend entity id translator CClientEntityIdTranslator IdTranslator; /// Impulse management CImpulseEncoder ImpulseEncoder; /// Last ack number received from client uint32 LastReceivedAck; /// NLMISC::TGameCycle LastReceivedGameCycle; uint32 LastDummy; uint32 LastSentDummy; // Estimation of available size in bit per cycle //CPropertyBaseType AvailableImpulseBitsize; /// uint NbActionsSentAtCycle; #ifdef NL_DEBUG uint8 MoveNumber; #endif }; /** * */ class CLimboClient { public: CLimboClient( CClientHost* client ) : AddrFrom(client->address()), Uid(client->Uid), UserName(client->UserName), UserPriv(client->UserPriv), UserExtended(client->UserExtended), LanguageId(client->LanguageId), QuitId(client->QuitId) { // Set limbo timeout start Timeout = NLMISC::CTime::getLocalTime(); LoginCookie = client->LoginCookie; } NLNET::CInetAddress AddrFrom; TUid Uid; std::string UserName, UserPriv, UserExtended, LanguageId; uint32 QuitId; NLMISC::TTime Timeout; NLNET::CLoginCookie LoginCookie; }; #endif // NL_CLIENT_HOST_H /* End of client_host.h */