1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "game_share/generic_xml_msg_mngr.h"
23 #include <nel/misc/stop_watch.h>
24 #include <nel/misc/path.h>
25 #include "fe_send_sub.h"
26 #include "game_share/simlag.h"
27 #include "game_share/action_factory.h"
28 #include "game_share/action_position.h"
29 #include "game_share/action_sync.h"
30 #include "game_share/action_disconnection.h"
31 #include "game_share/action_association.h"
32 #include "game_share/action_sint64.h"
33 #include "game_share/tick_event_handler.h"
35 #include "frontend_service.h"
39 #include "game_share/system_message.h"
42 using namespace NLNET
;
43 using namespace NLMISC
;
44 using namespace CLFECOMMON
;
46 uint SendCounterRatio
= 0;
47 uint SendCounterDelay
= 1000;
48 extern CGenericXmlMsgHeaderManager GenericXmlMsgHeaderMngr
;
55 void CFeSendSub::init( NLNET::CUdpSock
*datasock
, THostMap
*clientmap
, CHistory
*history
, CPrioSub
*priosub
)
57 nlassert( datasock
&& history
);
61 _ClientMap
= clientmap
;
65 _SendBuffers1
.resize( MaxNbClients
+1 );
66 _SendBuffers2
.resize( MaxNbClients
+1 );
67 _CurrentFillingBuffers
= &_SendBuffers1
;
68 _CurrentFlushingBuffers
= &_SendBuffers2
;
70 _MsgXmlMD5
= NLMISC::getMD5("msg.xml");
71 _DatabaseXmlMD5
= NLMISC::getMD5("database.xml");
73 /*_TotalBandwidth = totalbandwith; // initialized by setTotalBandwidth() and setClientBandwitdth()
74 _ClientBandwidth = clientbandwith; */
79 * Set client bandwidth per cycle in bytes
81 void CFeSendSub::setClientBandwidth( uint32 bytes
)
83 _ClientBitBandwidth
= bytes
* 8;
85 // Change the bandwidth of all logged clients
86 THostMap::iterator ihm
;
87 for ( ihm
=_ClientMap
->begin(); ihm
!=_ClientMap
->end(); ++ihm
)
89 GETCLIENTA(ihm
)->setClientBandwidth( _ClientBitBandwidth
);
96 * Setup headers for outgoing messages of current cycle
98 void CFeSendSub::prepareHeadersAndFillImpulses()
100 //CFrontEndService::instance()->HeadWatch.start();
102 TTime ctime
= CTime::getLocalTime();
104 THostMap::iterator iclient
;
105 for ( iclient
=_ClientMap
->begin(); iclient
!=_ClientMap
->end(); ++iclient
)
107 CClientHost
*client
= GETCLIENTA(iclient
);
109 if ( SendCounterRatio
> 0 && ctime
- client
->LastCounterTime
> SendCounterDelay
)
111 // Send several packets when checking for packet lost
112 client
->LastCounterTime
= ctime
;
115 for (i
=0; i
<SendCounterRatio
; ++i
)
118 GenericXmlMsgHeaderMngr
.pushNameToStream("DEBUG:COUNTER", bms
);
120 bms
.serial(client
->LastSentCounter
);
121 client
->LastSentCounter
++;
123 //nldebug( "DebugCounter %s", CInstanceCounterManager().getInstance().displayCounter( "CAction" ).c_str() );
124 CActionGeneric
*ag
= (CActionGeneric
*)CActionFactory::getInstance ()->create (INVALID_SLOT
, ACTION_GENERIC_CODE
);
127 CFrontEndService::instance()->addImpulseToClient (client
->clientId(), ag
, 1);
131 if ( client
->whenToSend() )
133 TOutBox
& outbox
= (*_CurrentFillingBuffers
)[client
->clientId()].OutBox
;
136 outbox
.resetBufPos();
138 // Manage connection state (probe or not)
139 switch (client
->ConnectionState
)
141 case CClientHost::Connected
:
143 client
->NbActionsSentAtCycle
= 0;
146 client
->setupOutBox( outbox
);
148 #ifdef INCLUDE_FE_STATS_IN_PACKETS
149 // send debug info in the message header
151 value
= CFrontEndService::instance()->UserLWatch
.getPartialAverage();
152 outbox
.serialAndLog1 (value
);
153 value
= CFrontEndService::instance()->CycleWatch
.getPartialAverage();
154 outbox
.serialAndLog1 (value
);
155 value
= CFrontEndService::instance()->ReceiveWatch
.getPartialAverage();
156 outbox
.serialAndLog1 (value
);
157 value
= CFrontEndService::instance()->SendWatch
.getPartialAverage();
158 outbox
.serialAndLog1 (value
);
159 outbox
.serialAndLog1 (client
->PrioAmount
);
160 uint16 seenentities
= 255 - client
->NbFreeEntityItems
;
161 outbox
.serialAndLog1 ( seenentities
);
162 /*float hpt = CFrontEndService::instance()->PrioSub.Prioritizer.hpThreshold();
163 outbox.serialAndLog1( hpt );*/
165 // Important: Set STAT_HEADER_SIZE if you change the debug info sent
167 // 2. Fill impulse actions
168 sint32 impulseFilledBits
= (sint32
)client
->ImpulseEncoder
.send( client
->sendNumber(), outbox
, _NbImpulseActions
);
170 // Impulsion flow control, takes into account:
171 // - the number of bits filled (possibly exceeding the max when sending forced actions (database))
172 // - the number of remaining actions not forced
173 client
->setImpulsionThrottle( impulseFilledBits
, (sint32
)client
->ImpulseEncoder
.maxBitSize(2), client
->ImpulseEncoder
.queueSize() );
177 case CClientHost::Synchronize
:
178 case CClientHost::ForceSynchronize
:
180 // Sending of Sync is now guaranteed at this cycle (see below) => fall back to Synchronize
181 if (client
->ConnectionState
== CClientHost::ForceSynchronize
)
182 client
->setSynchronizeState();
184 // send SYNC at defined frequency
185 NLMISC::TGameCycle tick
= CTickEventHandler::getGameCycle();
186 client
->setFirstSentPacket( client
->sendNumber()+1, tick
);
187 client
->setupSystemHeader( outbox
, SYSTEM_SYNC_CODE
);
190 TGameCycle sync
= client
->getSync();
191 outbox
.serialAndLog1(sync
);
192 TTime stime
= CTime::getLocalTime();
193 outbox
.serialAndLog1(stime
);
194 outbox
.serialAndLog1(client
->LastSentSync
);
196 outbox
.serialBuffer(_MsgXmlMD5
.Data
, sizeof(_MsgXmlMD5
.Data
));
197 outbox
.serialBuffer(_DatabaseXmlMD5
.Data
, sizeof(_DatabaseXmlMD5
.Data
));
199 nlinfo("FESEND: sent SYNC message to client %d - SYNC=%d, GameCycle=%d", client
->clientId(), sync
, CTickEventHandler::getGameCycle());
202 case CClientHost::Probe
:
203 // send PROBE at defined frequency
204 if (ctime
- client
->LastProbeTime
> PROBESendLatency
)
206 client
->setupSystemHeader( outbox
, SYSTEM_PROBE_CODE
);
207 client
->LastSentProbe
++;
208 outbox
.serialAndLog1(client
->LastSentProbe
);
209 client
->LastProbeTime
= ctime
;
210 nldebug("FESEND: sent PROBE message to client %d", client
->clientId());
211 client
->setIdleImpulsionThrottle();
216 //_OutputBits += outbox.getPosInBit();
220 nlassert( ! _ClientMap
->empty() );
222 //CFrontEndService::instance()->HeadWatch.stop();
227 * Fill prioritized actions into outgoing messages.
228 * Query priority subsystem for properties,
229 * retrieve them using mirror,
230 * fill client outboxes,
233 void CFeSendSub::fillPrioritizedActions()
235 #ifdef MEASURE_FRONTEND_TABLES
236 PosSentCntFrame
.setGameTick();
237 PosSentCntFrame
.reset( 0 );
240 // We don't take _TotalBitBandwith into account currently
242 /* Iterate on the clients
244 THostMap::iterator icm
;
245 for ( icm
=_ClientMap
->begin(); icm
!=_ClientMap
->end(); ++icm
)
247 CClientHost
*destclient
= GETCLIENTA(icm
);
249 // Do not fill if client is blocked
250 if ( destclient
->whenToSend() && (destclient
->ConnectionState
== CClientHost::Connected
) )
252 TOutBox
& outbox
= (*_CurrentFillingBuffers
)[destclient
->clientId()].OutBox
;
253 _PrioSub
->Prioritizer
.fillOutBox( *destclient
, outbox
);
254 _NbActions
+= destclient
->NbActionsSentAtCycle
;
255 destclient
->updateThrottle( outbox
);
258 // Open/close the send buffers, depending on the state clienthost->whenToSend().
259 // Note: the send buffers must not have a reference on the clienthost objects, because
260 // these can be destroyed when those are still processed in the background, therefore
261 // we browse the clients to enable/disable the send buffers.
262 // We set the current filling buffer only, just before swapping them, so that
263 // the changes will be taken into account at next flushing.
265 CSendBuffer
& sendbuffer
= (*_CurrentFillingBuffers
)[destclient
->clientId()];
266 sendbuffer
.enableSendBuffer( destclient
->whenToSend() );
267 //nldebug( "%u: client %hu %s", CTickEventHandler::getGameCycle(), destclient->clientId(), destclient->whenToSend()?"OPEN":"CLOSED" );
269 // Switch the send state of the client
270 destclient
->incSendCycle();
274 //nlassert( ! _ClientMap->empty() );
275 //float fullbuffers = (float)_OutputBits / (float)_ClientBitBandwidth;
277 CFrontEndService::instance()->SentActionsLastCycle
= _NbActions
+ _NbImpulseActions
;
278 //CFrontEndService::instance()->ScannedPropsLastCycle = _NbActions + nbnotsent - _NbImpulseActions;
280 // nlinfo( "FESEND: Sent %u actions (%u left ; %.2f full buffers, %.2f%%) including %u impulse (%0.2f priorities, %u per client)", _NbActions, nbnotsent, fullbuffers, fullbuffers/(float)_ClientMap->size()*100.0f, _NbImpulseActions, _PrioAmount, _NbActions / _ClientMap->size() );
282 /*for ( iclient=_ClientMap->begin(); iclient!=_ClientMap->end(); ++iclient )
284 nlinfo( "Client %u: size %u / %u", GETCLIENTA(iclient)->clientId(), GETCLIENTA(iclient)->OutBox.length(), _ClientBandwith );
287 //CFrontEndService::instance()->FillWatch.stop();
289 #ifdef MEASURE_FRONTEND_TABLES
290 PosSentCntFrame
.commit( PosSentCntClt1
);
299 void CFeSendSub::swapSendBuffers()
301 // Synchronization is done at module-level (in frontend_service.cpp)
303 // OBSOLETE: now the enabling of the send buffers depends on clienthost->whenToSend(), see below
304 /*// Before swapping, but after the previous flushing, enable the buffers of the new clients
305 // in the flushing buffers
306 typename TBuffersToEnable::iterator ib;
307 for ( ib=_BuffersToEnable.begin(); ib!=_BuffersToEnable.end(); ++ib )
309 enableSendBuffer( *ib );
311 _BuffersToEnable.clear();*/
314 if ( _CurrentFillingBuffers
== &_SendBuffers1
)
316 _CurrentFillingBuffers
= &_SendBuffers2
;
317 _CurrentFlushingBuffers
= &_SendBuffers1
;
321 _CurrentFillingBuffers
= &_SendBuffers1
;
322 _CurrentFlushingBuffers
= &_SendBuffers2
;
328 * Send the current outbox
330 inline void CFeSendSub::CSendBuffer::sendOutBox( NLNET::CUdpSock
*datasock
)
332 if ( OutBox
.length() != 0 )
334 sendUDP( datasock
, OutBox
.buffer(), OutBox
.length(), &DestAddress
);
335 //nlinfo( "Sent %u bytes to %s", OutBox.length(), DestAddress.asString().c_str() );
338 #ifdef MEASURE_SENDING
339 static sint32 loopcount
= 0;
341 static TTime lastdisplay
= CTime::getLocalTime();
342 TTime tn
= CTime::getLocalTime();
343 uint32 diff
= (uint32
)(tn
- lastdisplay
);
346 nlinfo("Sends by second: %.1f => LoopTime = %.2f ms LoopCount = %u Diff = %u ms Size=%u",(float)loopcount
* 1000.0f
/ (float)diff
, (float)diff
/ loopcount
, loopcount
, diff
, OutBox
.length());
355 * Send outgoing messages
356 * This can be executed by a background thread
358 void CFeSendSub::flushMessages()
361 //CFrontEndService::instance()->SndtWatch.start();
363 #ifdef MEASURE_SENDING
364 TTicks before
= CTime::getPerformanceTime();
367 TSendBuffers::iterator isb
;
368 for ( isb
=_CurrentFlushingBuffers
->begin(); isb
!=_CurrentFlushingBuffers
->end(); ++isb
)
370 if ( (*isb
).SBState
)
374 (*isb
).sendOutBox( _DataSock
);
376 //nldebug( "%u: SENDING NOW %u bytes to %s", CTickEventHandler::getGameCycle(), (*isb).OutBox.length(), (*isb).DestAddress.asString().c_str() );
378 catch (const ESocket
&)
380 nlwarning( "Could not send data to client %u", isb
-_CurrentFlushingBuffers
->begin() );
385 #ifdef MEASURE_SENDING
386 TTicks after
= CTime::getPerformanceTime();
387 static TTicks sum
= 0;
388 sum
+= (after
-before
);
389 static uint counter
= 0;
393 nlinfo( "Sending time: %.3f ms", CTime::ticksToSecond( sum
/20 ) * 1000.0 );
405 * Deprecated: replaced by modules (see initModules() in frontend_service.cpp)
407 /*void CFeSendSub::update()
409 if ( _ClientMap->empty() )
420 fillPrioritizedActions();
424 _PrioSub->endCycle(); // disables the priority marked to
426 nlinfo( "FETIME: Time of sending: Headers=%u Fill=%u Send=%u EndCycle=%u",
427 CFrontEndService::instance()->HeadWatch.getDuration(),
428 CFrontEndService::instance()->FillWatch.getDuration(),
429 CFrontEndService::instance()->SndtWatch.getDuration(),
430 CFrontEndService::instance()->EndCWatch.getDuration() );
434 NLMISC_VARIABLE( uint
, SendCounterRatio
, "Set the check counter send ratio, e.g. the number of counter sent at a time (0 means don't send)");
435 NLMISC_VARIABLE( uint
, SendCounterDelay
, "Set the check counter send delay, e.g. the time between 2 counter send rafale (in ms)");