// 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 . #include "stdpch.h" #include "property_dispatcher.h" #include "vision_array.h" #include "property_id_translator.h" #include "frontend_service.h" #include "fe_stat.h" //#include using namespace std; using namespace NLMISC; using namespace CLFECOMMON; /* * Constructor */ CPropertyDispatcher::CPropertyDispatcher() : _VisionArray( NULL ), _PropTranslator( NULL ), _ClientId( 0 ) /*_HPThreshold( 0 ), _HPTDelta( 0 ),*/ //_NbSentParcelsInCycle( 0 ) /*NbSetPrio( 0 ), NbReenable( 0 )*/ { _NextParcel.Prio = DISABLE_PRIORITY; _NextParcel.Offset = INVALID_OFFSET; } /* * Initialization */ void CPropertyDispatcher::init( TClientId clientid ) { _ClientId = clientid; _VisionArray = &(CFrontEndService::instance()->PrioSub.VisionArray); _PropTranslator = &(CFrontEndService::instance()->PrioSub.PropTranslator); // Initialize highest priority threshold from theorical maxratio //_HPThreshold = CFrontEndService::instance()->PrioSub.Prioritizer.MaxRatio / NB_PRIORITIES; } /* * Remove from the property dispatcher shelves, or does nothing if offset is INVALID_OFFSET */ void CPropertyDispatcher::removeProp( const TPropParcelPtr& parcelptr ) { // Remove corresponding item in shelves (do nothing if there is no item yet) if ( parcelptr.Offset != INVALID_OFFSET ) { //nlinfo( "FEPRIO: Removing prop from dispatcher: %u-%u", parcelptr.Prio, parcelptr.Offset ); TPropertyShelf& shelf = getShelf( parcelptr.Prio ); //TMPDEBUG if ( parcelptr.Offset < shelf.size() ) { TPropParcel& parcel = getParcel( shelf, parcelptr.Offset ); // Remove parcel TPropParcel& lastone = lastParcel( shelf ); if ( &parcel != &lastone ) { parcel = lastone; _VisionArray->updateLink( _ClientId, parcel, parcelptr.Offset ); } shelf.pop_back(); } else { nlwarning( "Cannot removeProp with invalid offset %hd", parcelptr.Offset ); } } } /* * Remove all corresponding properties from the property dispatcher */ void CPropertyDispatcher::removePropsFromShelves( TClientId clientid, TCLEntityId ceid ) { sint ppi = 0; TPropParcelPtr& parcelptr = _VisionArray->prioLoc( clientid, ceid, ppi ); removeProp( parcelptr ); parcelptr.Prio = DISABLE_PRIORITY; parcelptr.Offset = INVALID_OFFSET; // Skip special props for ( ppi=3; ppi!=MAX_PROPERTIES_PER_ENTITY; ++ppi ) { TPropParcelPtr& parcelptr = _VisionArray->prioLoc( clientid, ceid, ppi ); removeProp( parcelptr ); parcelptr.Prio = DISABLE_PRIORITY; parcelptr.Offset = INVALID_OFFSET; } } /* * Make a new enabled parcel in the shelf corresponding to parcelptr.Prio and fill the parcelptr.Offset */ void CPropertyDispatcher::addProp( TPropParcelPtr& parcelptr, TCLEntityId ceid, TPropIndex propindex ) { TPropParcel parcel; parcel.CeId = ceid; parcel.PropIndex = propindex; parcel.Enabled = true; // Add parcel and fill the offset field TPropertyShelf& shelf = getShelf( parcelptr.Prio ); shelf.push_back( parcel ); parcelptr.Offset = shelf.size() - 1; //printShelves( "after addProp", true ); } /* * Initialize dispatch cycle */ void CPropertyDispatcher::initDispatcherCycle( /*bool rescalePriorities*/ ) { // Adjust threshold for highest priority selection /*if ( rescalePriorities ) { adjustHPThreshold(); }*/ // Set the pointer ready to advance to the first parcel _NextParcel.Prio = INITIAL_PRIORITY; _NextParcel.Offset = -1; //_NbSentParcelsInCycle = 0; // Move pointer to first parcel incNextParcel(); // Display debug info //printShelves( "after initDispatcherCycle", false ); //printShelfSizes(); } /* * Increment pointer to next parcel */ void CPropertyDispatcher::incNextParcel() { if ( _NextParcel.Offset < nbParcels(_NextParcel.Prio) - 1 ) { // Advance to the next parcel in the same shelf ++_NextParcel.Offset; // Check if the current parcel is enabled, otherwise advance to next one if ( ! getParcel( _NextParcel ).Enabled ) { incNextParcel(); } } else { if ( _NextParcel.Prio < LAST_PRIO_SENT ) { // "Carriage return" to the next shelf ++_NextParcel.Prio; _NextParcel.Offset = 0; // Skip any empty shelves skipEmptyShelves(); } else { // Don't go past LAST_PRIO_SENT _NextParcel.Offset = INVALID_OFFSET; } } } /* * Skip any empty shelves */ inline void CPropertyDispatcher::skipEmptyShelves() { // Skip empty shelves while ( _PropShelves[_NextParcel.Prio].empty() && (_NextParcel.Prio < LAST_PRIO_SENT) ) { ++_NextParcel.Prio; } // Detect end of data if ( _PropShelves[_NextParcel.Prio].empty() ) { _NextParcel.Offset = INVALID_OFFSET; } else { // Check if the current parcel is enabled, otherwise advance to next one if ( ! getParcel( _NextParcel ).Enabled ) { incNextParcel(); } } } /* * Return which property to send next, or NULL if there is no more. */ //const TPropParcel *CPropertyDispatcher::getNextParcel() /* * Set status: true if the latest parcel got by getNextParcel() can be sent in the current cycle, otherwise false * If true, the parcel gets disabled. * Must be called after getNextParcel() every time getNextParcel() is called */ void CPropertyDispatcher::setParcelStatusTrue() { // Status is set when the parcel was sent if ( _NextParcel.Offset != INVALID_OFFSET ) { //++_NbSentParcelsInCycle; TPropParcel& parcel = getParcel( _NextParcel ); // Continuous or discreet property ? //if ( _PropTranslator->isContinuous( _VisionArray->getEntityIndex( _ClientId, parcel.CeId ), parcel.PropIndex ) ) if ( parcel.PropIndex < FIRST_DISCREET_PROPINDEX ) { // Continuous: disable the parcel parcel.Enabled = false; #ifdef MEASURE_FRONTEND_TABLES // Debug: this is ok because the position is a common prop and its propertyid equals the propindex if ( (_ClientId == 1) && (parcel.PropIndex == PROPERTY_POSITION) ) { PosSentCntFrame.SeenEntities[parcel.CeId] = 1; } #endif } else { // Discreet: ask parcel removal and set update status to Updating TPropState& propstate = _VisionArray->propState( _ClientId, parcel ); addToRemoveList( &propstate ); propstate.UpdateStatus = Updating; // DEBUG DISPLAY //nlinfo( "FESEND: Sent discrete property %hu to client %hu slot %hu", propertyid, _ClientId, (uint16)parcel.CeId ); } } // Advance to next parcel incNextParcel(); } /* * Remove the parcels that have been added to the remove list (call it after a cycle) */ void CPropertyDispatcher::flush() { // Remove all parcels referenced in the remove list TRemoveList::iterator irl; for ( irl=_RemoveList.begin(); irl!=_RemoveList.end(); ++irl ) { // Remove parcel removeProp( *(*irl) ); // Remove link (*irl)->Prio = DISABLE_PRIORITY; (*irl)->Offset = INVALID_OFFSET; } // Clear the remove list _RemoveList.clear(); } /* * Return the number of enabled parcels in a shelf */ TPropParcelOffset CPropertyDispatcher::nbEnabledParcels( TPriority prio ) { nlassert( prio <= NB_PRIORITIES ); sint32 nb = 0; TPropertyShelf::iterator ips; for ( ips=_PropShelves[prio].begin(); ips!=_PropShelves[prio].end(); ++ips ) { if ( (*ips).Enabled ) ++nb; } return (TPropParcelOffset)nb; } /* * Adjust HPThreshold (dichotomic) */ /*void CPropertyDispatcher::adjustHPThreshold() { uint32 nbhpparcels = (uint32)nbEnabledParcels( HIGHEST_PRIORITY ); float hpoldvalue = _HPThreshold; //nlinfo( "NbHP=%u NbSentParcelsInCycle=%u", (uint32)nbParcels( HIGHEST_PRIORITY ), _NbSentParcelsInCycle ); if ( nbhpparcels < _NbSentParcelsInCycle / 2 ) { // Increase threshold when HIGHEST_PRIORITY can be filled more if ( _HPTDelta < 0 ) { _HPThreshold = _HPThreshold - _HPTDelta/2.0f; //nlinfo( "FEPRIO: Stabilizing HPThreshold (+) to %.2f", _HPThreshold ); } else { _HPThreshold *= 2.0f; //nlinfo( "FEPRIO: Rising HPThreshold to %.2f, %u estimated actions", _HPThreshold, CFrontEndService::instance()->SentActionsLastCycle ); //printShelfSizes(); } } else { // Decrease threshold when HIGHEST_PRIORITY is overloaded if ( nbhpparcels > _NbSentParcelsInCycle ) { if ( _HPTDelta > 0 ) { _HPThreshold = _HPThreshold - _HPTDelta/2.0f; //nlinfo( "FEPRIO: Stabilizing HPThreshold (-) to %.2f", _HPThreshold ); } else { _HPThreshold /= 2.0f; // decrease threshold if HIGHEST_PRIORITY is crowded //nlinfo( "FEPRIO: Lowering HPThreshold to %.2f, %u estimated actions", _HPThreshold, CFrontEndService::instance()->SentActionsLastCycle ); //printShelfSizes(); } } } float maxratio = CFrontEndService::instance()->PrioSub.Prioritizer.MaxRatio; if ( _HPThreshold >= maxratio ) { // Fix threshold if it is too big after being calculated in adjustHPThreshold() _HPThreshold = maxratio* 0.8f; _HPTDelta = 0.0f; //nlinfo( "FEPRIO: Fixing HPThreshold to %.2f", _HPThreshold ); } //nlinfo( "FEPRIO: HPThreshold is %.2f", _HPThreshold ); else { _HPTDelta = _HPThreshold - hpoldvalue; } }*/ /* * Return the amount of priorities used corresponding to the specified number of actions (e.g. 1.2 means priority 0 is full and priority 1 is 20% filled) */ float CPropertyDispatcher::getPrioRatio() { // FEATURE DISABLED (_NbSentParcelsInCycle not calculated) uint32 nbactions = 0;//_NbSentParcelsInCycle; //nlinfo( "NbSent: %u size0: %u", _NbSentParcelsInCycle, _PropShelves[0].size() ); float ratio = 0.0f; uint32 amount = 0; TPriority p; for ( p=0; p!=NB_PRIORITIES; ++p ) { uint32 nbenabledinshelf = nbEnabledParcels(p); if ( nbactions - amount > nbenabledinshelf ) { // Accumulate amount of full shelf amount += _PropShelves[p].size(); ratio += 1.0f; } else { // Accumulate amount of last shelf (decimals) if ( ! _PropShelves[p].empty() ) { ratio += (float)(nbactions - amount) / (float)nbenabledinshelf; } //comment Ben nlinfo( "FEPRIO: %u HP of %u total => ratio %.1f last=%u", _PropShelves[0].size(), nbactions, ratio, p ); return ratio; } } return ratio; } /* * Display the contents of the shelves */ void CPropertyDispatcher::printShelves( const char *title, bool checkIntegrity, bool proptext, bool hidedisabled ) const { bool integrity = true; //stringstream ss; string str; string check; if ( checkIntegrity ) { check = " checking integrity"; } //ss << "Client: " << _ClientId << /*" HPThreshold: " << _HPThreshold <<*/ " Next parcel: "; str += "Client: " + NLMISC::toString(_ClientId) + /*" HPThreshold: " << NLMISC::toString(_HPThreshold) <<*/ " Next parcel: "; if ( _NextParcel.Offset == -1 ) { //ss << "Ready to start" << endl; str += "Ready to start\n"; } else if ( _NextParcel.Offset == INVALID_OFFSET ) { //ss << "INVALID_OFFSET (prio=" << _NextParcel.Prio << ")" << endl; str += "INVALID_OFFSET (prio=" + NLMISC::toString(_NextParcel.Prio) + ")\n"; } else { //ss << "prio " << _NextParcel.Prio << " offset " << _NextParcel.Offset << endl; str += "prio " + NLMISC::toString(_NextParcel.Prio) + " offset " + NLMISC::toString(_NextParcel.Offset) + "\n"; } //ss << "Property shelves " << title << check << ":" << endl; str += "Property shelves " + NLMISC::toString(title) + NLMISC::toString(check) + ":\n"; TPriority p; // Scan all shelves for ( p=0; p!=NB_PRIORITIES; ++p ) { if ( ! _PropShelves[p].empty() ) { //ss << p << ':'; str += NLMISC::toString(p) + ':'; TPropertyShelf::const_iterator ipr; // Scan all parcels for ( ipr=_PropShelves[p].begin(); ipr!=_PropShelves[p].end(); ++ipr ) { const TPropParcel& parcel = (*ipr); if ( (!hidedisabled) || parcel.Enabled ) { //ss << ' ' << parcel.CeId << ' '; str += " " + NLMISC::toString(parcel.CeId) + " "; if ( proptext ) //ss << getPropText( parcel.PropIndex ); str += NLMISC::toString(getPropText( parcel.PropIndex )); else //ss << parcel.PropIndex; str += NLMISC::toString(parcel.PropIndex); if ( !hidedisabled ) //ss << ' ' << parcel.Enabled; str += " " + NLMISC::toString(parcel.Enabled); //ss << " -"; str += " -"; } // Check integrity (debugging feature) if ( checkIntegrity ) { TPropParcelPtr parcelptr = _VisionArray->propState( _ClientId, parcel ); if ( ! (parcelptr.Prio == p) && (parcelptr.Offset == TPropParcelOffset(ipr-_PropShelves[p].begin())) ) { integrity = false; } } } //ss << endl; str += "\n"; } } // ss << ends; nlwarning( "FEPRIO: %s", str.c_str() ); if ( ! integrity ) { nlerror( "Property Dispatcher: integrity check failed" ); } } /* * Display the sizes of the shelves */ void CPropertyDispatcher::printShelfSizes() const { sint prio; //InfoLog->display( "FEPRIO: Threshold: %.2f Parcels by prio:", _HPThreshold ); InfoLog->display( "FEPRIO: Parcels by prio:" ); for ( prio=HIGHEST_PRIORITY; prio!=NB_PRIORITIES; ++prio ) { InfoLog->displayRaw( " %u", _PropShelves[prio].size() ); } InfoLog->displayRawNL( "" ); } /* * Count the number of clients or properties of a certain type in the property shelves */ void CPropertyDispatcher::displayCounts( TPropIndex propindex ) { sint prio = 0; sint ofst = 0; sint matching = 0; sint enabled_matching = 0; sint discrete_matching = 0; sint self_matching = 0; // slot 0 while ( prio < NB_PRIORITIES ) { for ( ofst = 0; ofst != (sint)_PropShelves[prio].size(); ++ofst ) { bool propok = ( (propindex == 0xFF) || (propindex == _PropShelves[prio][ofst].PropIndex) ); if ( propok ) { ++matching; if ( _PropShelves[prio][ofst].Enabled) { ++enabled_matching; } if ( _PropShelves[prio][ofst].PropIndex > LAST_CONTINUOUS_PROPERTY ) { ++discrete_matching; } if ( _PropShelves[prio][ofst].CeId == 0 ) { ++self_matching; } } } ++prio; } if ( propindex == 0xFF ) nlinfo( "FEPRIO: Searching for all property indexes" ); else // warning: mixing property/propindex (TEMP) nlinfo( "FEPRIO: Searching for propindex %hu (%s)", (uint16)propindex, getPropText(propindex) ); nlinfo( "FEPRIO: Found %d matching properties, including %d enabled, %d discrete, %d self-vision", matching, enabled_matching, discrete_matching, self_matching ); } NLMISC_COMMAND( displayPropShelves, "Display the shelves of the property dispatcher", "[|0] [0/1] [1/0] [0/1]" ) { // Parse arguments TClientId clientid = 0; bool hidedisabled = false; bool check = false; bool proptext = true; if ( args.size() > 0 ) { clientid = atoi(args[0].c_str()); if ( args.size() > 1 ) { hidedisabled = (atoi(args[1].c_str()) != 0); if ( args.size() > 2 ) { check = (atoi(args[2].c_str()) != 0); if ( args.size() > 3 ) { proptext = (atoi(args[3].c_str()) != 0); } } } } // Display THostMap& clientmap = CFrontEndService::instance()->instance()->receiveSub()->clientMap(); THostMap::iterator icm; for ( icm=clientmap.begin(); icm!=clientmap.end(); ++icm ) { if ( (clientid == 0) || (clientid == GETCLIENTA(icm)->clientId()) ) { GETCLIENTA(icm)->PropDispatcher.printShelves( "command", check, proptext, hidedisabled, &log ); } } return true; } NLMISC_COMMAND( displayPropShelfSizes, "Display the sizes of the shelves of the property dispatcher", "[|0]" ) { // Parse arguments TClientId clientid = 0; if ( args.size() > 0 ) { clientid = atoi(args[0].c_str()); } // Display THostMap& clientmap = CFrontEndService::instance()->instance()->receiveSub()->clientMap(); THostMap::iterator icm; for ( icm=clientmap.begin(); icm!=clientmap.end(); ++icm ) { if ( (clientid == 0) || (clientid == GETCLIENTA(icm)->clientId()) ) { GETCLIENTA(icm)->PropDispatcher.printShelfSizes(&log); } } return true; } NLMISC_COMMAND( countClientsOrPropertiesInShelves, "Count the number of clients or properties of a certain type in the property shelves", "[ []]" ) { // Parse arguments TClientId clientid = 0; TPropIndex propindex = 0xFF; if ( args.size() > 0 ) { clientid = atoi(args[0].c_str()); if ( args.size() > 1 ) { propindex = atoi(args[1].c_str()); } } if ( clientid == 0 ) log.displayNL( "FEPRIO: Searching for all clients" ); else log.displayNL( "FEPRIO: Searching for client %hu", clientid ); // Count THostMap& clientmap = CFrontEndService::instance()->instance()->receiveSub()->clientMap(); THostMap::iterator icm; for ( icm=clientmap.begin(); icm!=clientmap.end(); ++icm ) { if ( (clientid == 0) || (clientid == GETCLIENTA(icm)->clientId()) ) { GETCLIENTA(icm)->PropDispatcher.displayCounts( propindex, &log ); } } return true; }