// 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_NETWORK_CONNECTION_H #define NL_NETWORK_CONNECTION_H #include "nel/misc/types_nl.h" #include "nel/misc/bit_set.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "time_client.h" #include "game_share/entity_types.h" #include "property_decoder.h" #include "impulse_decoder.h" #include "game_share/action.h" #include "game_share/ryzom_entity_id.h" #include "game_share/action_block.h" #include "game_share/loot_harvest_state.h" /* * ENABLE_INCOMING_MSG_RECORDER for recording/replaying incoming network messages */ #undef ENABLE_INCOMING_MSG_RECORDER #ifdef ENABLE_INCOMING_MSG_RECORDER #include #endif /* // UNUSED const uint PropertiesPerEntity = 16; const uint EntitiesPerClient = 256; */ class CCDBNodeBranch; namespace CLFECOMMON { class CActionGeneric; class CActionGenericMultiPart; } // Hierachical timer H_AUTO_DECL ( RZ_Client_Network_Connection_Set_Reference_Position ) //#define MEASURE_FE_SENDING extern const char * ConnectionStateCStr [9]; /** * Exception thrown when the sending returns a "blocking operation interrupted". */ class EBlockedByFirewall : public NLNET::ESocket {}; struct TNewEntityInfo { void reset() { DataSetIndex = CLFECOMMON::INVALID_CLIENT_DATASET_INDEX; Alias = 0; } /// Compressed dataset row CLFECOMMON::TClientDataSetIndex DataSetIndex; /// Alias (only for mission giver NPCs) uint32 Alias; }; // *************************************************************************** /** * Abstracts the connection client towards front-end * \author Benjamin Legros, Olivier Cado * \author Nevrax France * \date 2001-2002 */ class CNetworkConnection { public: enum { /* * DO LEAVE ENOUGH ROOM FOR FUTURE PROPERTIES ! */ AddNewEntity = 32, RemoveOldEntity, ConnectionReady, LagDetected, ProbeReceived }; struct TVPNodeClient : public CLFECOMMON::TVPNodeBase { struct TSlotContext { CNetworkConnection *NetworkConnection; CLFECOMMON::TCLEntityId Slot; NLMISC::TGameCycle Timestamp; }; // Static data static TSlotContext SlotContext; /// TVPNodeClient() : TVPNodeBase() {} TVPNodeClient *a() { return (TVPNodeClient*)VPA; } TVPNodeClient *b() { return (TVPNodeClient*)VPB; } TVPNodeClient *parent() { return (TVPNodeClient*)VPParent; } /// void decodeDiscreetProperties( NLMISC::CBitMemStream& msgin ) { msgin.serialBitAndLog( BranchHasPayload ); if ( BranchHasPayload ) { if ( isLeaf() ) { SlotContext.NetworkConnection->decodeDiscreetProperty( msgin, PropIndex ); } else { if ( a() ) a()->decodeDiscreetProperties( msgin ); if ( b() ) b()->decodeDiscreetProperties( msgin ); } } } void makeChildren() { VPA = new TVPNodeClient(); VPA->VPParent = this; VPB = new TVPNodeClient(); VPB->VPParent = this; } void makeDescendants( uint nbLevels ) { makeChildren(); if ( nbLevels > 1 ) { a()->makeDescendants( nbLevels-1 ); b()->makeDescendants( nbLevels-1 ); } } void deleteBranches() { if ( a() ) { a()->deleteBranches(); delete a(); } if ( b() ) { b()->deleteBranches(); delete b(); } } void deleteA() { if ( a() ) { a()->deleteBranches(); delete a(); } } void deleteB() { if ( b() ) { b()->deleteBranches(); delete b(); } } }; /// Additional info for position updates struct TPositionInfo { /** * Only for positions (property index 0): interval, in game cycle unit, between the current * position update and the next predicted one. In most cases the next real position update * will occur just before the predicted interval elapses. * * If the interval cannot be predicted yet (e.g. before having received two updates * for the same entity), PredictedInterval is set to ~0. */ NLMISC::TGameCycle PredictedInterval; /// Is position interior (only for position) bool IsInterior; }; /** * A property change */ class CChange { public: /// Constructor CChange( CLFECOMMON::TCLEntityId id = 0, uint8 prop = 255, NLMISC::TGameCycle gc = 0 ) : ShortId(id), Property(prop), GameCycle(gc) {} /// Slot CLFECOMMON::TCLEntityId ShortId; /// Property index uint8 Property; /// The tick when the property changed, in server time NLMISC::TGameCycle GameCycle; // These contextual additional properties must be set by hand (not by constructor) union { /// Position additional information (prediction & interior) TPositionInfo PositionInfo; /// Additional information when creating an entitys TNewEntityInfo NewEntityInfo; }; }; /// The states of the connection to the server (if you change them, change ConnectionStateCStr) enum TConnectionState { NotInitialised = 0, // nothing happened yet NotConnected, // init() called Authenticate, // connect() called, identified by the login server Login, // connecting to the frontend, sending identification Synchronize, // connection accepted by the frontend, synchronizing Connected, // synchronized, connected, ready to work Probe, // connection lost by frontend, probing for response Stalled, // server is stalled Disconnect, // disconnect() called, or timeout, or connection closed by frontend Quit // quit() called }; typedef void (*TImpulseCallback)(NLMISC::CBitMemStream &, CLFECOMMON::TPacketNumber packet, void *arg); static bool LoggingMode; public: /// Constructor CNetworkConnection(); /// Destructor virtual ~CNetworkConnection(); /// @name Connection initialisation //@{ /** * Inits the client connection to the front-end. * \param cookie it s the cookie in string format that was passed to the exe command line (given by the nel_launcher) * \param addr it s the front end address in string format that was passed to the exe command line (given by the nel_launcher) */ void init(const std::string &cookie, const std::string &addr); /** * Sets the cookie and front-end address, resets the connection state. */ void initCookie(const std::string &cookie, const std::string &addr); /** * Connects to the front-end (using or not the login system, depending on what was specified at init()) * \param result the message returned in case of an error * Returns true if no error occured. */ bool connect(std::string &result); /** * Sets the callback called when a generic impulse comes */ void setImpulseCallback(TImpulseCallback callback, void *argument = NULL); //@} /// @name Connection management //@{ /** * Tells if the client is yet connected or not */ bool isConnected(); /** * Gets the connection state */ TConnectionState getConnectionState() const { return _ConnectionState; } /** * Updates the whole connection with the frontend. * * Behaviour in login state when a firewall does not grant the sending: * - When a sending is refused (error "Blocking operation interrupted") * the exception EBlockedByFirewall in thrown. The first time, a time-out is armed. * - In a later attempt, if the time-out expired, the function sets state to * Disconnect, then throws EBlockedByFirewall. * * \return bool 'true' if data were sent/received. */ bool update(); /** * Disconnects the current connection */ void disconnect(); //@} /** * Quit the game till the connection is reset */ void quit(); /// @name Database management //@{ /** * Set database entry point */ void setDataBase(CCDBNodeBranch *database); //@} /// @name Push and Send //@{ /** * Buffers a bitmemstream, that will be converted into a generic action, to be sent later to the server (at next update). */ void push (NLMISC::CBitMemStream &msg); /** * Buffers an action to be sent at next update (or later updates if network overload occurs) */ void push(CLFECOMMON::CAction *action); /** * Buffers a target action (set targetOrPickup to true for target, false for pick-up */ void pushTarget(CLFECOMMON::TCLEntityId slot, LHSTATE::TLHState targetOrPickup); /** * Send */ void send(NLMISC::TGameCycle); /** * Send last information without changes (only acknowedges frontend) */ void send(); /** * Clear not acknownledged actions in sending buffer */ void flushSendBuffer() { _Actions.clear(); } /** * Set player reference position */ // void setReferencePosition(const NLMISC::CVectorD &position) { _ImpulseDecoder.setReferencePosition(position); } //@} /// @name Temp methods // @{ const std::vector &getChanges() { return _Changes; } void clearChanges() { _Changes.clear(); } // @} /// @name Client time hints // @{ NLMISC::TTime getCurrentClientTime() { return _CurrentClientTime; } // the time currently played at this frame (in the past) NLMISC::TGameCycle getCurrentClientTick() { return _CurrentClientTick; } // the tick for the current frame (also in the past) NLMISC::TGameCycle getCurrentServerTick() { return _CurrentServerTick; } // the last tick sent by the server. NLMISC::TTime getMachineTimeAtTick() { return _MachineTimeAtTick; } // the machine time at the beginning of the current tick NLMISC::TTicks getMachineTicksAtTick() { return _MachineTicksAtTick; } sint32 getMsPerTick() { return _MsPerTick; } NLMISC::TGameCycle getSynchronize() { return _Synchronize; } uint32 getBestPing() { return _BestPing; } uint32 getPing() const { return _InstantPing; } NLMISC::TTime getLCT() { return _LCT; } // Lag compensation time NLMISC::TTime getUpdateTime() { return _UpdateTime; } NLMISC::TTicks getUpdateTicks() { return _UpdateTicks; } // Return an estimated smooth server tick. Increment only and accelerate/decelerate according to network sint64 getCurrentSmoothServerTick() const { return _CurrentSmoothServerTick; } // Convert a GameCycle to a SmoothServerTick sint64 convertToSmoothServerTick(NLMISC::TGameCycle t) const; // @} /// Set the Lag Compensation Time in ms (default: 1000) void setLCT( NLMISC::TTime lct ) { _LCT = lct; } /// Allocation statistics void displayAllocationStats(); /// Get Allocation statistics std::string getAllocationStats(); /// @name Network Stats Methods // @{ uint32 getSentBytes() { return _TotalSentBytes; } uint32 getPartialSentBytes() { uint32 ret = _PartialSentBytes; _PartialSentBytes = 0; return ret; } uint32 getReceivedBytes() { return _TotalReceivedBytes; } uint32 getPartialReceivedBytes() { uint32 ret = _PartialReceivedBytes; _PartialReceivedBytes = 0; return ret; } bool getConnectionQuality() { return _ConnectionQuality; } NLMISC::TTime getTimeSinceLastReceivedPacket() { return NLMISC::CTime::getLocalTime()-_LastReceivedNormalTime; } /// Return the average billed download rate in kbps, including all headers (UDP+IP+Ethernet) float getMeanDownload() { return (_MeanDownload.mean(ryzomGetLocalTime ())+20+8+14)*0.008f; } /// Return the average billed upload rate in kbps, including all headers (UDP+IP+Ethernet) float getMeanUpload() { return (_MeanUpload.mean(ryzomGetLocalTime ())+20+8+14)*0.008f; } /// Return the packet loss average float getMeanPacketLoss() { NLMISC::TTime time = ryzomGetLocalTime (); _MeanPackets.check(time); _MeanLoss.check(time); float dpk = _MeanPackets.Values.empty() ? 0.0f : _MeanPackets.Values.back().second-_MeanPackets.Values.front().second+1; float tloss = _MeanLoss.Content; return (dpk > 0.0f) ? std::min(100.0f, tloss*100.0f/dpk) : 0.0f; } /// Get total lost packets uint32 getTotalLostPackets() const { return _TotalLostPackets; } // @} /// Get the NLMISC::CEntityId from the TCLEntityId CLFECOMMON::TSheetId get(CLFECOMMON::TCLEntityId id) const { return _PropertyDecoder.sheet(id); } /// Get the TCLEntityId from the NLMISC::CEntityId sint get(CLFECOMMON::TSheetId id) const { TIdMap::const_iterator it = _IdMap.find(id); return (it != _IdMap.end()) ? (*it).second : -1; } /// Set player's reference position void setReferencePosition(const NLMISC::CVectorD &position) { H_AUTO_USE ( RZ_Client_Network_Connection_Set_Reference_Position ) _PropertyDecoder.setReferencePosition( position ); } /// Get local IP address const NLNET::CInetAddress& getAddress() { return _Connection.localAddr(); } /// Get socket NLNET::CUdpSock& getConnection() { return _Connection.UdpSock; } /// Get userId uint32 getUserId() { if(_LoginCookie.isValid()) return _LoginCookie.getUserId(); else return 0xFFFFFFFF; } /// Get connection state c_string const char * getConnectionStateCStr() { return ConnectionStateCStr[_ConnectionState]; } /// Return the login cookie const NLNET::CLoginCookie& getLoginCookie() const { return _LoginCookie; } #ifdef ENABLE_INCOMING_MSG_RECORDER /// Return 'true' if currently recording. bool isRecording() const {return _RecordIncomingMessagesOn;} /// Return 'true' if currently replaying. bool isReplaying() const {return _ReplayIncomingMessagesOn;} /// Start/stop recording the incoming messages (in online mode) void setRecordingMode( bool onOff, const std::string& filename = ""); /// Start/stop replaying the incoming messages (in offline mode) void setReplayingMode( bool onOff, const std::string& filename = ""); #endif void decodeDiscreetProperty( NLMISC::CBitMemStream& msgin, CLFECOMMON::TPropIndex propIndex ); const std::string &getFrontendAddress() { return _FrontendAddress; } protected: /// The current state of the connection TConnectionState _ConnectionState; /// Connection Quality check bool _ConnectionQuality; /// The address of the frontend service std::string _FrontendAddress; /// The cookie for the login service NLNET::CLoginCookie _LoginCookie; /// The UDP connection to the frontend NLNET::CUdpSimSock _Connection; /// The receive buffer uint32 _ReceiveBuffer[65536]; /// The impulse decoder CImpulseDecoder _ImpulseDecoder; /// The callback for generic actions TImpulseCallback _ImpulseCallback; /// The callback argument void *_ImpulseArg; /// Position update tick queues (used for statistical interval prediction), one per slot std::deque _PosUpdateTicks[256]; /// Position update interval sorted sets (used for statistical interval prediction), one per slot std::multiset _PosUpdateIntervals[256]; /// MD5 hash keys of msg.xml and database.xml NLMISC::CHashKeyMD5 _MsgXmlMD5; NLMISC::CHashKeyMD5 _DatabaseXmlMD5; // if prec don't work, those may (if the server run on windows, prec won't work) NLMISC::CHashKeyMD5 _AltMsgXmlMD5; NLMISC::CHashKeyMD5 _AltDatabaseXmlMD5; /// @name Network statistics //@{ class CMeanComputer { public: CMeanComputer() : MeanPeriod(2000), Content(0.0f) {} std::deque< std::pair > Values; uint32 MeanPeriod; // in ms float Content; // checks all values have valid time (inside the mean period) void check(NLMISC::TTime time) { while (!Values.empty() && Values.front().first < time-MeanPeriod) { Content -= Values.front().second; Values.pop_front(); } } // updates the mean with a new value and time void update(float value, NLMISC::TTime time) { if (!Values.empty() && Values.back().first > time) return; check(time); Values.push_back(std::make_pair(time, value)); Content += value; } // gets the mean float mean(NLMISC::TTime time) { check(time); return Content*1000.0f/(float)MeanPeriod; } }; /// The total received length (from the beginning) uint32 _TotalReceivedBytes; /// The partial received length (since last read) uint32 _PartialReceivedBytes; /// The total sent length uint32 _TotalSentBytes; /// Partial sent length uint32 _PartialSentBytes; /// Total lost packets uint32 _TotalLostPackets; /// Mean download (payload bytes) CMeanComputer _MeanDownload; /// Mean upload (payload bytes) CMeanComputer _MeanUpload; /// Mean packets CMeanComputer _MeanPackets; /// Mean lost CMeanComputer _MeanLoss; //@} /// The property decoder CPropertyDecoder _PropertyDecoder; /// The database entry CCDBNodeBranch *_DataBase; /// @name Header message decoding //@{ /// Was the header decoded for the last received message? bool _DecodedHeader; /// The received number lastly decoded CLFECOMMON::TPacketNumber _CurrentReceivedNumber; /// The message is in system mode bool _SystemMode; /// Did we received a probe since last update bool _ReceivedSync; uint _TotalMessages; //@} /// @name Client and Server packet numbering management //@{ CLFECOMMON::TPacketNumber _CurrentSendNumber; // number that will be used to send the next packet CLFECOMMON::TPacketNumber _LastReceivedNumber; // number of the last packet receive from the server uint32 _AckBitMask; // this mask contains ack of old messages received by the server uint32 _LastAckBit; // a remember of the status of the previous received packet NLMISC::TTime _LastReceivedTime; // last time the client received something from the server NLMISC::CBitSet _LongAckBitField; CLFECOMMON::TPacketNumber _LastAckInLongAck; NLMISC::TGameCycle _LastSentCycle; CLFECOMMON::TPacketNumber _LastReceivedPacketInBothModes; NLMISC::TTime _LastReceivedNormalTime; // last time the client received valid normal message from the server uint8 _ImpulseMultiPartNumber; //@} /// @name Client and Server time management //@{ /// The stamps queues typedef std::deque > TStampQueue; // used for lag compensation NLMISC::TTime _CurrentClientTime; // the time currently played at this frame (in the past) NLMISC::TGameCycle _CurrentClientTick; // the tick for the current frame (also in the past) NLMISC::TGameCycle _CurrentServerTick; // the last tick sent by the server. NLMISC::TTime _MachineTimeAtTick; // the machine time at the beginning of the current tick NLMISC::TTicks _MachineTicksAtTick; sint32 _MsPerTick; NLMISC::TGameCycle _Synchronize; uint32 _BestPing; uint32 _InstantPing; NLMISC::TTime _LCT; // Lag compensation time TStampQueue _PacketStamps; NLMISC::TTime _UpdateTime; NLMISC::TTicks _UpdateTicks; NLMISC::TTime _LastSentSync; NLMISC::TTime _LastSendTime; //@} std::list _Actions; /// Changes since std::vector _Changes; // TEMP uint32 _DummySend; static bool _Registered; /// @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, CNetworkConnection *parent); }; std::vector _GenericMultiPartTemp; //@} /// @name NLMISC::CEntityId handling (for gamedev) //@{ class CHash { public: static const size_t bucket_size = 4; static const size_t min_buckets = 8; CHash() {} size_t operator () (const CLFECOMMON::TSheetId &id) const { return id; } inline bool operator() (const CLFECOMMON::TSheetId &id1, const CLFECOMMON::TSheetId &id2) const { return (size_t)id1 < (size_t)id2; } }; typedef CHashMap TIdMap; TIdMap _IdMap; //@} TVPNodeClient *_VisualPropertyTreeRoot; /// \name Smooth ServerTick mgt. // @{ sint64 _CurrentSmoothServerTick; NLMISC::TTime _SSTLastLocalTime; void updateSmoothServerTick(); // @} void reinit(); private: /// Set MsPerTick value void setMsPerTick(sint32 msPerTick); /// Builds the CBitMemStream from the next incoming packet bool buildStream(NLMISC::CBitMemStream &msgin); /// @name Client automaton states and actions //@{ // void sendSystemLogin(); bool stateLogin(); NLMISC::TTime _LatestLoginTime; // void receiveSystemSync(NLMISC::CBitMemStream &msgin); void sendSystemAckSync(); bool stateSynchronize(); NLMISC::TTime _LatestSyncTime; uint32 _LatestSync; // void receiveNormalMessage(NLMISC::CBitMemStream &msgin); void sendNormalMessage(); bool stateConnected(); CLFECOMMON::TPacketNumber _LastReceivedAck; uint _NormalPacketsReceived; // void receiveSystemProbe(NLMISC::CBitMemStream &msgin); void sendSystemAckProbe(); bool stateProbe(); std::vector _LatestProbes; CLFECOMMON::TPacketNumber _LatestProbe; NLMISC::TTime _LatestProbeTime; // void receiveSystemStalled(NLMISC::CBitMemStream &msgin); bool stateStalled(); // void sendSystemDisconnection(); // bool stateQuit(); void receiveSystemAckQuit(NLMISC::CBitMemStream &msgin); void sendSystemQuit(); uint32 _QuitId; bool _ReceivedAckQuit; NLMISC::TTime _LatestQuitTime; // void reset(); void initTicks(); //@} // bool decodeHeader(NLMISC::CBitMemStream &msgin, bool checkMessageNumber = true); // void buildSystemHeader(NLMISC::CBitMemStream &msgout); // void genericAction (CLFECOMMON::CActionGeneric *ag); void genericAction (CLFECOMMON::CActionGenericMultiPart *agmp); // void statsSend(uint32 bytes); void statsReceive(uint32 bytes); bool associationBitsHaveChanged( CLFECOMMON::TCLEntityId slot, uint32 associationBits ) { bool res = ((uint16)associationBits != _PropertyDecoder.associationBits( slot )); _PropertyDecoder.associationBits( slot ) = (uint16)associationBits; return res; } void decodeVisualProperties( NLMISC::CBitMemStream& msgin ); /// Return true if there is some messages to replay (in replay mode) bool dataToReplayAvailable(); uint32 _AssociationBits; #ifdef ENABLE_INCOMING_MSG_RECORDER NLMISC::COFile _RecordedMessagesOut; NLMISC::CIFile _RecordedMessagesIn; NLMISC::TGameCycle _NextClientTickToReplay; // ~0 when no more message NLMISC::CBitMemStream _NextMessageToReplay; // loaded at beginning of update(), read in buildStream() bool _RecordIncomingMessagesOn; bool _ReplayIncomingMessagesOn; #endif // friend struct CGenericMultiPartTemp; }; #endif // NL_NETWORK_CONNECTION_H /* End of network_connection.h */