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 "client_host.h"
22 #include "frontend_service.h"
24 #include "game_share/entity_types.h" // required for ifdef
26 #include "id_impulsions.h"
27 #include "uid_impulsions.h"
30 using namespace NLMISC
;
31 using namespace CLFECOMMON
;
34 #ifdef HALF_FREQUENCY_SENDING_TO_CLIENT
35 //#pragma message ("HALF_FREQUENCY_SENDING_TO_CLIENT")
37 //#pragma message ("FULL_FREQUENCY_SENDING_TO_CLIENT")
43 * Helps sorting by distance the entities seen by a client, by priority
45 struct TComparePairsByDistance
47 TComparePairsByDistance( CVisionArray
*va
, TClientId clientId
) : _VisionArray(va
), _ClientId(clientId
) {}
49 bool operator() ( CLFECOMMON::TCLEntityId first
, CLFECOMMON::TCLEntityId second
)
51 return ( _VisionArray
->getPairState( _ClientId
, first
).DistanceCE
< _VisionArray
->getPairState( _ClientId
, second
).DistanceCE
);
55 CVisionArray
*_VisionArray
;
59 inline TPairState
& CClientHost::getPairState(TCLEntityId e
)
61 return CFrontEndService::instance()->PrioSub
.VisionArray
.getPairState( _ClientId
, e
);
64 inline const TPairState
& CClientHost::getPairState(TCLEntityId e
) const
66 return CFrontEndService::instance()->PrioSub
.VisionArray
.getPairState( _ClientId
, e
);
71 * Prepare a clean new outbox with current values
74 void CClientHost::setupOutBox( TOutBox
& outbox
)
76 // only fill outbox headers if connected
77 // in system mode, fill is done manually because packets are not sent at each update
78 nlassert ( ConnectionState
== Connected
);
79 // a. Add the send number belonging to the destination client
80 uint32 sendnumber
= getNextSendNumber();
81 outbox
.serialAndLog1( sendnumber
);
84 bool systemMode
= false;
85 outbox
.serialBitAndLog( systemMode
);
87 // b. Add the latest receive number belonging to the destination client
88 outbox
.serialAndLog1( _ReceiveNumber
);
90 // c. Add the "toggle" bit for important actions
91 //OutBox.serialBit( _ToggleBit );
95 * Prepare a clean system header
97 void CClientHost::setupSystemHeader( TOutBox
& outbox
, uint8 code
)
99 // Only setup header for special states such as Synchronize, Probe...
100 nlassert(ConnectionState
!= Connected
);
102 // checks the outbox is really cleared
103 nlassert(outbox
.length() == 0);
105 // a. Add the send number belonging to the destination client
106 uint32 sendnumber
= getNextSendNumber();
107 outbox
.serialAndLog1( sendnumber
);
110 bool systemMode
= true;
111 outbox
.serialBitAndLog( systemMode
);
113 // c. System message code
114 outbox
.serialAndLog1( code
);
119 * Set receive time now
121 void CClientHost::setReceiveTimeNow()
123 _ReceiveTime
= CTime::getLocalTime();
128 * Initialize the client bandwidth
130 void CClientHost::initClientBandwidth()
132 setClientBandwidth( CFrontEndService::instance()->sendSub()->clientBandwidth() );
137 * CClientHost: Compute host stats
139 void CClientHost::computeHostStats( const TReceivedMessage
& msgin
, uint32 currentcounter
, bool updateAcknowledge
)
141 if ( _ReceiveNumber
== 0xFFFFFFFF )
143 _FirstReceiveNumber
= currentcounter
;
144 if (updateAcknowledge
)
145 _ReceiveNumber
= currentcounter
;
147 else if ( currentcounter
<= _ReceiveNumber
)
151 else if ( currentcounter
> _ReceiveNumber
+1 )
153 _DatagramLost
+= currentcounter
-(_ReceiveNumber
+1);
154 if (updateAcknowledge
)
155 _ReceiveNumber
= currentcounter
;
157 else if (updateAcknowledge
)
159 _ReceiveNumber
= currentcounter
;
167 const char *associationStateToString( uint8 as
)
171 case TPairState::UnusedAssociation
: return "Unused"; break;
172 case TPairState::AwaitingAssAck
: return "Assocn"; break;
173 case TPairState::NormalAssociation
: return "Normal"; break;
174 case TPairState::AwaitingDisAck
: return "Disass"; break;
178 case CClientEntityIdTranslator::CEntityInfo::UnusedAssociation : return "Unused"; break;
179 case CClientEntityIdTranslator::CEntityInfo::AwaitingAssAck : return "Assocn"; break;
180 case CClientEntityIdTranslator::CEntityInfo::NormalAssociation : return "Normal"; break;
181 case CClientEntityIdTranslator::CEntityInfo::AwaitingDisAck : return "Disass"; break;
183 /*case CClientEntityIdTranslator::CEntityInfo::SubstitutionBeforeDisAck : return "Substitution before dis ack"; break;
184 case CClientEntityIdTranslator::CEntityInfo::SubstitutionAfterDisAck : return "Substitution after dis ack"; break;
185 case CClientEntityIdTranslator::CEntityInfo::CancelledSubstitution : return "Cancelled substitution"; break;*/
186 default: return "INVALID ASSOCIATION CODE";
191 inline std::string
getUserName( const TEntityIndex
& entityIndex
)
194 if ( entityIndex
.isValid() )
196 TClientId clientId
= CFrontEndService::instance()->receiveSub()->EntityToClient
.getClientId( entityIndex
);
197 if ( clientId
!= INVALID_CLIENT
)
199 CClientHost
*client
= CFrontEndService::instance()->receiveSub()->clientIdCont()[clientId
];
202 name
= string(" ") + client
->UserName
;
213 void CClientHost::displayClientProperties( bool full
, bool allProps
, bool sortByDistance
, NLMISC::CLog
*log
) const
215 // General properties
216 bool invision
= false;
217 TSheetId sheetId
= INVALID_SHEETID
;
218 string sheetIdS
= "_";
219 CEntity
*sentity
= NULL
;
220 if ( (_EntityIndex
.isValid()) && (_EntityIndex
.getIndex() < (uint32
)TheDataset
.maxNbRows()) )
222 sentity
= TheEntityContainer
->getEntity( _EntityIndex
);
226 CMirrorPropValueRO
<uint32
> propSheet( TheDataset
, _EntityIndex
, DSPropertySHEET
);
227 sheetId
= propSheet();
228 if ( sentity
->propertyIsInitialized( PROPERTY_SHEET
, DSPropertySHEET
, _EntityIndex
, (TYPE_SHEET
*)NULL
) )
230 sheetIdS
= CSheetId(sheetId
).toString();
235 const char *notset
= "(not set)";
236 log
->displayNL( "C%hu E%s %s %s sheet %s Uid %u, %s, %s", _ClientId
, _EntityIndex
.isValid() ? toString("%u", _EntityIndex
.getIndex()).c_str() : notset
, _Id
.isUnknownId() ? notset
: _Id
.toString().c_str(), getEntityName( _EntityIndex
).c_str(), (sheetId
!=~0) ? toString( "%s (%u)", sheetIdS
.c_str(), sheetId
).c_str() : notset
, Uid
, UserName
.c_str(), invision
?"in vision":"not in vision" );
239 log
->displayNL( "Position (m): %d %d %d - Local: %d %d %d - Mode: %s", sentity
->posXm( _EntityIndex
), sentity
->posYm( _EntityIndex
), sentity
->posZm( _EntityIndex
), sentity
->posLocalXm( _EntityIndex
), sentity
->posLocalYm( _EntityIndex
), sentity
->posLocalZm( _EntityIndex
), (sentity
->z( _EntityIndex
)&0x1)?"Relative":"Absolute" );
241 log
->displayNL( "Host %s, %s, %s", _Address
.asString().c_str(), _Disconnected
?"disconnected":"connected", _Synchronized
?"synchronized":"not synchronized" );
242 log
->displayNL( "Latest activity %u ms ago, state %s", (uint32
)(CTime::getLocalTime()-_ReceiveTime
),
243 ConnectionState
==Synchronize
?"SYNC":ConnectionState
==Connected
?"CONN":ConnectionState
==Probe
?"PROBE":ConnectionState
==ForceSynchronize
?"FORCE_SYNC":"DISC" );
244 #ifdef HALF_FREQUENCY_SENDING_TO_CLIENT
249 if ( _EntityIndex
.isValid())
251 CMirrorPropValueRO
<uint16
> availableImpulseBitsize( TheDataset
, _EntityIndex
, DSFirstPropertyAvailableImpulseBitSize
);
252 log
->displayNL( "Bit bandwidth usage feeding client at %d Hz: %d bps (max 13312), current throttle %d; including impulsion %d, database throttle %d",
253 freq
, _BitBandwidthUsageAvg
*freq
, getCurrentThrottle()*freq
, _BitImpulsionUsageAvg
*freq
, availableImpulseBitsize
*freq
);
256 log
->displayNL( "Nb messages in impulse queues: %u %u %u", ImpulseEncoder
.queueSize(0), ImpulseEncoder
.queueSize(1), ImpulseEncoder
.queueSize(2) );
261 log
->displayNL( "Vision slots (%hu free):", NbFreeEntityItems
);
267 for ( e
=0; e
!=MAX_SEEN_ENTITIES_PER_CLIENT
; ++e
)
270 //if ( (assState = CFrontEndService::instance()->PrioSub.VisionArray.getAssociationState( this, (TCLEntityId)e )) != CClientEntityIdTranslator::CEntityInfo::UnusedAssociation ) // CHANGED BEN
271 if ( getPairState( (TCLEntityId
)e
).AssociationState
!= TPairState::UnusedAssociation
)
273 slots
.push_back( e
);
277 if ( sortByDistance
)
279 sort( slots
.begin(), slots
.end(), TComparePairsByDistance( &(CFrontEndService::instance()->PrioSub
.VisionArray
), _ClientId
) );
282 vector
<sint
>::iterator islot
;
283 for ( islot
=slots
.begin(); islot
!=slots
.end(); ++islot
)
285 displaySlotProperties( *islot
, allProps
, log
);
288 // Client is seen by * on this front-end
289 /*if ( (_EntityIndex.isValid()) && (_EntityIndex < TheDataSet.maxNbRows()) )
291 log->displayNL( "Visibility of this entity by other clients on this front-end:" );
292 TObserverList::const_iterator iol;
293 for ( iol= CFrontEndService::instance()->PrioSub.VisionProvider.observerList( _EntityIndex ).begin();
294 iol!=CFrontEndService::instance()->PrioSub.VisionProvider.observerList( _EntityIndex ).end();
297 log->displayNL( "* Seen by client %hu using slot %hu", (*iol).ClientId, (uint16)(*iol).Slot );
302 log->displayNL( "Cannot access the observer list" );
305 TheEntityContainer
->mirrorInstance().displayRows( _Id
, *log
);
311 * display nlinfo for one slot
313 void CClientHost::displaySlotProperties( CLFECOMMON::TCLEntityId e
, bool full
, NLMISC::CLog
*log
) const
315 //const CClientEntityIdTranslator::CEntityInfo& info = IdTranslator.getInfo( (TCLEntityId)e );
316 TEntityIndex seenEntityIndex
= getPairState( e
).EntityIndex
;
317 if ( ! seenEntityIndex
.isValid() )
319 CEntity
*seenEntity
= TheEntityContainer
->getEntity( seenEntityIndex
);
320 CAction::TValue vlsx
, vlsy
, vlsz
;
321 CFrontEndService::instance()->history()->getPosition( _ClientId
, e
, vlsx
, vlsy
, vlsz
); // always the absolute pos
322 TCoord lsx
= ((sint32
)vlsx
)/1000, lsy
= ((sint32
)vlsy
)/1000, lsz
= ((sint32
)vlsz
)/1000;
323 TCoord sentMileage
= CFrontEndService::instance()->history()->getMileage( _ClientId
, e
);
324 TCoord mileageDelta
= seenEntity
->Mileage
- sentMileage
;
325 const TPairState
& pairState
= getPairState( e
);
326 string sheetId
, sheetIdS
;
327 if ( seenEntity
->propertyIsInitialized( PROPERTY_SHEET
, DSPropertySHEET
, seenEntityIndex
, (TYPE_SHEET
*)NULL
) )
329 CMirrorPropValueRO
<uint32
> propSheet( TheDataset
, seenEntityIndex
, DSPropertySHEET
);
330 sheetId
= toString("%u", propSheet() );
331 sheetIdS
= CSheetId(propSheet()).toString();
338 const CEntityId
& seenEntityId
= TheDataset
.getEntityId( seenEntityIndex
);
339 log
->displayNL( "* Slot %d E%u %s %s st%s %s sheet %s (%s) dist(m) %0.1f %s pos %d %d %d, sent %d %d %d %sdelta %d prio %.1f ab %hu",
341 seenEntityIndex
.getIndex(),
342 (seenEntityId
.getType()==RYZOMID::player
)?(string("PLAYER")+getUserName(seenEntityIndex
)).c_str():RYZOMID::toString( (RYZOMID::TTypeId
)seenEntityId
.getType() ).c_str(),
343 seenEntityId
.toString().c_str(),
344 associationStateToString( getPairState( e
).AssociationState
),
345 getEntityName(seenEntityIndex
).c_str(),
348 (float)pairState
.DistanceCE
/1000.0f
,
349 getDirection( seenEntity
, seenEntityIndex
),
350 seenEntity
->posXm( seenEntityIndex
),
351 seenEntity
->posYm( seenEntityIndex
),
352 seenEntity
->posZm( seenEntityIndex
),
354 (sentMileage
==0)?"(not sent yet) ":"", mileageDelta
,
355 pairState
.getPrio(), (uint16
)(pairState
.AssociationChangeBits
& 0x3) );
359 log
->displayNL( "| Visual properties of E%u: ", seenEntityIndex
.getIndex() );
360 seenEntity
->displayProperties( seenEntityIndex
, log
, _ClientId
, e
);
366 * Return the cardinal direction from the player to the seen entity
368 const char * CClientHost::getDirection( CEntity
*seenEntity
, const TEntityIndex
& seenEntityIndex
) const
370 if ( !_EntityIndex
.isValid() )
372 CEntity
*entity
= TheEntityContainer
->getEntity( _EntityIndex
);
373 if ( entity
== NULL
)
376 float dx
= (float)(seenEntity
->X() - entity
->X());
377 float dy
= (float)(seenEntity
->Y() - entity
->Y());
378 float angle
= (float)fmod( atan2(dy
, dx
) + (2*Pi
), 2*Pi
);
379 uint direction
= (uint
) ( 8.0f
*angle
/((float)Pi
) );
380 nlassert ( direction
<16 );
381 const char * txts
[] =
401 return txts
[direction
];
406 * display nlinfo (1 line only)
408 void CClientHost::displayShortProps(NLMISC::CLog
*log
) const
410 // General properties
411 bool invision
= false;
412 if ( (_EntityIndex
.isValid()) && (_EntityIndex
.getIndex() < (uint32
)TheDataset
.maxNbRows()) )
414 CEntity
*sentity
= TheEntityContainer
->getEntity( _EntityIndex
);
415 if ( sentity
!= NULL
)
418 log
->displayNL( "Client %hu: E%u %s '%s' uid %u '%s' %s, %s", _ClientId
, _EntityIndex
.getIndex(), _Id
.toString().c_str(), getEntityName(_EntityIndex
).c_str(), Uid
, UserName
.c_str(), invision
?"in vision":"not in vision", _Address
.asString().c_str() );
426 void CClientHost::setEId( const NLMISC::CEntityId
& assigned_id
)
435 CClientHost::~CClientHost()
437 //REMOVE_PROPERTY_FROM_EMITER( _Id, uint16, "AvailImpulseBitsize" );
438 //_Id = CEntityId::Unknown;
443 * Set the entity index
445 void CClientHost::setEntityIndex( const TEntityIndex
& ei
)
448 CFrontEndService::instance()->PrioSub
.VisionArray
.setEntityIndex( _ClientId
, 0, ei
);
452 void CClientHost::CGenericMultiPartTemp::set (CActionGenericMultiPart
*agmp
, CClientHost
*client
)
454 if (NbBlock
== 0xFFFFFFFF)
456 // new GenericMultiPart
457 NbBlock
= agmp
->NbBlock
;
461 Temp
.resize(NbBlock
);
462 BlockReceived
.resize(NbBlock
);
463 for (uint i
= 0; i
< NbBlock
; i
++)
464 BlockReceived
[i
] = false;
467 nlassert (NbBlock
== agmp
->NbBlock
);
468 nlassert (NbBlock
> agmp
->Part
);
470 // check if the block was already received
471 if (BlockReceived
[agmp
->Part
])
476 Temp
[agmp
->Part
] = agmp
->PartCont
;
477 BlockReceived
[agmp
->Part
] = true;
480 TempSize
+= (uint32
)agmp
->PartCont
.size();
482 if (NbCurrentBlock
== NbBlock
)
484 // reform the total action
485 CBitMemStream
bms(true);
487 uint8
*ptr
= bms
.bufferToFill (TempSize
);
489 for (uint i
= 0; i
< Temp
.size (); i
++)
491 memcpy (ptr
, &(Temp
[i
][0]), Temp
[i
].size());
492 ptr
+= Temp
[i
].size();
495 NbBlock
= 0xFFFFFFFF;
497 if ( client
->eId().isUnknownId() )
499 routeImpulsionUidFromClient(bms
, client
->Uid
, client
->LastReceivedGameCycle
);
503 routeImpulsionIdFromClient(bms
, client
->eId(), client
->LastReceivedGameCycle
);
508 void CClientHost::resetClientVision()
510 // Get all entities seen by this client
512 for ( e
=0; e
!=MAX_SEEN_ENTITIES_PER_CLIENT
; ++e
)
514 TEntityIndex entityindex
= getPairState( (TCLEntityId
)e
).EntityIndex
;
516 //if ( CFrontEndService::instance()->PrioSub.VisionArray.getAssociationState( this, (TCLEntityId)e ) != CClientEntityIdTranslator::CEntityInfo::UnusedAssociation ) // CHANGED BEN
517 if (getPairState( (TCLEntityId
)e
).AssociationState
!= TPairState::UnusedAssociation
)
519 // Remove from observer list: the clients who see the entity (and its ceid for them)
520 /*if ( entityindex != INVALID_ENTITY_INDEX )
522 CFrontEndService::instance()->PrioSub.VisionProvider.removeFromObserverList( entityindex, clienthost->clientId(), (TCLEntityId)e );
526 nlwarning( "Invalid entity index while trying to remove slot %hu from observer list of leaving client %hu", (uint16)e, clienthost->clientId() );
529 // Remove pair from history
530 CFrontEndService::instance()->history()->removeEntityOfClient( e
, clientId() );
532 // No need to remove Id because they are stored in the client object, which will be deleted
535 CFrontEndService::instance()->PrioSub
.Prioritizer
.removeAllEntitiesSeenByClient( clientId() );
538 for ( e
=0; e
!=MAX_SEEN_ENTITIES_PER_CLIENT
; ++e
)
541 getPairState(e
).resetItem();
542 getPairState(e
).resetAssociation();