// 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 "pair_selector.h" #include "prio_sub.h" #include "frontend_service.h" using namespace std; using namespace CLFECOMMON; /* * Comparison operator by distance */ bool TComparePairCE::operator()( const TPairCE& e1, const TPairCE& e2 ) { return ( _VisionArray->distanceCE( e1.ClientId, e1.CeId ) < _VisionArray->distanceCE( e2.ClientId, e2.CeId ) ); } /* * Constructor */ CPairSelector::CPairSelector() : PseudoSort( false ), SelStrategy( Scoring ), NbPairsPerRange( 2000 ), NbPairsToSelect( 2000 ), MinNbPairsToSelect( 2000 ), _CurrentRangeIndex( 0 ), _BeginIndex( 0 ), _NextPairIndex( -1 ), _NPSDelta( 0 ), _SelGenerator( NULL ), _VisionArray( NULL ) { } /* * Initialization */ void CPairSelector::init( CVisionArray *va ) { _VisionArray = va; // Create first range TPairRange newrange; newrange.IndexInRanges = 0; newrange.NumberOfElements = 0; newrange.DistThreshold = CFrontEndService::instance()->VisibilityDistance; _Ranges.push_back( newrange ); } /* * Add to the selector vector (TODO: the distance could be used for insertion) */ void CPairSelector::addPair( TClientId clientid, TCLEntityId ceid, TCoord distance ) { // Add to the vector _PairsByDistance.push_back( TPairCE( clientid, ceid ) ); // Update ranges if ( (TPairIndex)_PairsByDistance.size() == lastRange().beginIndex( _Ranges ) + NbPairsPerRange ) { // Add one range addRange(); } else { // Update last range (TODO: insert into the good range) ++lastRange().NumberOfElements; } } /* * Remove from the container */ void CPairSelector::removePair( TClientId clientid, TCLEntityId ceid ) { // Find the pair in the vector // (TODO: evaluate the need of storing an index in VisionArray, faster find but slower sort) TPairVector::iterator ip = find( _PairsByDistance.begin(), _PairsByDistance.end(), TPairCE( clientid, ceid ) ); nlassert( ip != _PairsByDistance.end() ); // Remove it if ( ip != _PairsByDistance.end()-1) { *ip = _PairsByDistance.back(); } _PairsByDistance.pop_back(); // Adjust last range for pseudosorting adjustRangesAfterDeleting(); } /* * Remove from the container all pairs with the specified clientid */ void CPairSelector::removeAllPairs( TClientId clientid ) { sint nbremoved = 0; TPairVector::iterator ip = _PairsByDistance.begin(); while ( ip < _PairsByDistance.end() ) { // Find matching pair if ( (*ip).ClientId == clientid ) { // Remove it (replace it by the last one) if ( ip != _PairsByDistance.end()-1 ) { *ip = _PairsByDistance.back(); } _PairsByDistance.pop_back(); ++nbremoved; } else { ++ip; } } // Adjust ranges sint i; for ( i=0; i!=nbremoved; ++i ) { adjustRangesAfterDeleting(); } } /* * Update ranges after deleting a pair */ void CPairSelector::adjustRangesAfterDeleting() { TPairRange *lastrange = &lastRange(); if ( nbRanges() == 1 ) { // Decrement size of last range nlassert( lastrange->NumberOfElements != 0 ); --lastrange->NumberOfElements; } else { // Remove last range if empty while ( (lastrange->NumberOfElements == 0 ) && (nbRanges() != 1) ) { removeLastRange(); lastrange = &lastRange(); } // Decrement size of last range nlassert( lastrange->NumberOfElements != 0 ); --lastrange->NumberOfElements; } } /* * Sort or pseudosort the pairs, and initialize selection cycle. * Must be called before a series of selectNextPair(). */ void CPairSelector::sortPairs() { if ( empty() ) return; // Sort or pseudosort if ( nbPairs() > NbPairsPerRange ) { // Pseudosort PseudoSort = true; // Get current range ++_CurrentRangeIndex; if ( _CurrentRangeIndex >= nbRanges() ) { _CurrentRangeIndex = 0; } TPairRange& currentrange = _Ranges[_CurrentRangeIndex]; nlassert( _CurrentRangeIndex == currentrange.IndexInRanges ); // Get thresholds and boundaries uint32 nbraised = 0; uint32 nblowered = 0; TCoord minDistThreshold = (currentrange.IndexInRanges != 0) ? _Ranges[_CurrentRangeIndex-1].DistThreshold : 0; TCoord maxDistThreshold = (currentrange.IndexInRanges != nbRanges()-1) ? currentrange.DistThreshold : MAX_COORD; TPairIndex beginIndex = currentrange.beginIndex( _Ranges ); TPairIndex endIndex = beginIndex + currentrange.NumberOfElements; nlassert( endIndex <= nbPairs() ); //comment Ben nlinfo( "FEPRIO: Pseudosorting %u elements from %u to %u of range %u (%u couples)...", currentrange.NumberOfElements, beginIndex, endIndex, currentrange.IndexInRanges, nbPairs() ); // Test every pair with the thresholds to know if they need to be raised or lowered TPairIndex index; for ( index = beginIndex; index < endIndex; ) { TPairCE &entry = _PairsByDistance[index]; TCoord d = _VisionArray->distanceCE( entry.ClientId, entry.CeId ); if (d < minDistThreshold) { // Raise if (index != beginIndex) swap( _PairsByDistance[beginIndex], _PairsByDistance[index] ); ++beginIndex; ++index; ++nbraised; } else if (d > maxDistThreshold) { // Lower if (index != endIndex-1) swap( _PairsByDistance[endIndex-1], _PairsByDistance[index] ); --endIndex; ++nblowered; } else { // The pair remains where it is ++index; } } // Adjust threshold if there are too many elements in the current range if ( currentrange.NumberOfElements > NbPairsPerRange + NbPairsPerRange/2 ) { if ( _CurrentRangeIndex == 0 ) { currentrange.DistThreshold /= 2; } else { currentrange.DistThreshold = (_Ranges[_CurrentRangeIndex-1].DistThreshold + currentrange.DistThreshold) / 2; } } // The number of removed elements is equal to // the number of elements raised plus the number of elements lowered currentrange.NumberOfElements -= (nbraised+nblowered); if (currentrange.IndexInRanges != 0) _Ranges[_CurrentRangeIndex-1].NumberOfElements += nbraised; if (currentrange.IndexInRanges != nbRanges()-1) _Ranges[_CurrentRangeIndex+1].NumberOfElements += nblowered; } else { // Sort PseudoSort = false; //comment Ben nlinfo( "FEPRIO: Sorting %u elements...", _PairsByDistance.size() ); std::sort( _PairsByDistance.begin(), _PairsByDistance.end(), TComparePairCE( _VisionArray ) ); } // Initialize selection cycle calcNbPairsToSelect(); } /* * Select the next pair to prioritize, or return NULL if no more pairs for this cycle */ const TPairCE *CPairSelector::selectNextPair() { //nlassert( ! _PairsByDistance.empty() ); // Set new index ++_NextPairIndex; if ( _NextPairIndex >= nbPairs() ) { // End of table, prepare falling back to beginning for next cycle _SelGenerator->init( getNbLevels() ); selectNextLevel(); return NULL; } else if ( _NextPairIndex >= _BeginIndex + NbPairsToSelect ) { // Prepare next level for next cycle selectNextLevel(); return NULL; } return &_PairsByDistance[_NextPairIndex]; } /* * Add a range for pseudosort, and resample the distance thresholds. */ void CPairSelector::addRange() { // Add one range TPairRange& range = lastRange(); TPairRange newrange; newrange.IndexInRanges = range.IndexInRanges + 1; newrange.NumberOfElements = 1; newrange.DistThreshold = CFrontEndService::instance()->VisibilityDistance; nlinfo( "FEPRIO: Adding range %u, index %u, threshold %d", _Ranges.size(), newrange.IndexInRanges, newrange.DistThreshold ); _Ranges.push_back( newrange ); // Resample distance thresholds uint32 nbranges = nbRanges(); nlassert( nbranges > 1 ); // size is 1 at initialization float resampleratio = (float)(nbranges-1) / (float)nbranges; uint i; for ( i=0; i!=nbranges-1; ++i ) { _Ranges[i].DistThreshold = (TCoord)((float)_Ranges[i].DistThreshold * resampleratio); } if ( nbRanges() > 30 ) { nlwarning( "More than 3 seconds will be needed to pseudosort all ranges" ); } printRanges( false ); nlassertex( lastRange().beginIndex( _Ranges ) + lastRange().NumberOfElements == nbPairs(), ("Total size: %u", nbPairs()) ); } /* * Remove the last range (for pseudosort) when it's empty, and resample the distance thresholds. */ void CPairSelector::removeLastRange() { // Delete last range (if there are more than 1 range) nlassert( (nbRanges() > 1) && (lastRange().NumberOfElements == 0) ); nlinfo( "FEPRIO: Removing last range %u", nbRanges() ); _Ranges.pop_back(); // Resample distance thresholds uint32 nbranges = nbRanges(); float resampleratio = (float)(nbranges+1) / (float)(nbranges); uint i; for ( i=0; i!=nbranges; ++i ) { // TODO: check the cast is good (float precision) _Ranges[i].DistThreshold = (TCoord)((float)_Ranges[i].DistThreshold * resampleratio); } printRanges( false ); } /* * Set the selection strategy */ void CPairSelector::setSelectionStrategy( TSelectionStrategy strat ) { SelStrategy = strat; setupSelectionGenerator( DEFAULT_P2C_CEILING ); } /* * Setup selection generator, using SelStrategy */ void CPairSelector::setupSelectionGenerator( ISelectionGenerator::TSelectionLevel ceiling ) { // Delete existing generator if already created if ( _SelGenerator ) { delete _SelGenerator; } // Create new generator switch ( SelStrategy ) { case Power2WithCeiling: _SelGenerator = new CP2CGenerator( ceiling ); break; case Scoring: _SelGenerator = new CScoringGenerator(); break; default: nlstop; } // Init generator _SelGenerator->init( getNbLevels() ); } /* * Select next level. Call selectNextPair() after selectNextLevel(). */ void CPairSelector::selectNextLevel() { _BeginIndex = _SelGenerator->getNext() * NbPairsToSelect; _NextPairIndex = _BeginIndex - 1; // next increment will advance to the begin index value //comment Ben nlinfo( "FEPRIO: Changed selection level to [ %u , %u ]", _BeginIndex, _BeginIndex + NbPairsToSelect ); } /* * Calculate NbPairsToSelect */ void CPairSelector::calcNbPairsToSelect() { /* * Calculate NbPairsToSelect taking into account the user loop time * * Note: instead of taking MinNbPairsToSelect as the minimum value, we should * take into account the number of clients x the number of actions per client, otherwise * some messages may be not entirely filled. */ uint32 nroldvalue = NbPairsToSelect; if ( (CFrontEndService::instance()->UserDurationPAverage > 100) && (NbPairsToSelect > MinNbPairsToSelect) ) { // Decrease the number of pairs to select, to speed up the process if ( _NPSDelta > 0 ) { NbPairsToSelect = max( (TPairIndex)(NbPairsToSelect - _NPSDelta/2), (TPairIndex)MinNbPairsToSelect ); } else { NbPairsToSelect = max( (TPairIndex)(NbPairsToSelect / 2), (TPairIndex)MinNbPairsToSelect ); } //comment Ben nlinfo( "FEPRIO: NbRowsToComputePerCycle is now %u (-), %u", NbPairsToSelect, CFrontEndService::instance()->UserDurationPAverage ); } else if ( (CFrontEndService::instance()->UserDurationPAverage < 10) && (NbPairsToSelect < nbPairs()) ) { // Increase the number of pairs to select, because there is some CPU time available if ( _NPSDelta < 0 ) { NbPairsToSelect = min( (TPairIndex)(NbPairsToSelect - _NPSDelta/2), nbPairs() ); } else { NbPairsToSelect = min( (TPairIndex)(NbPairsToSelect * 2), nbPairs() ); } //comment Ben nlinfo( "FEPRIO: NbRowsToComputePerCycle is now %u (+)", NbPairsToSelect ); } _NPSDelta = NbPairsToSelect - nroldvalue; // Update the selection generator _SelGenerator->changeNbLevels( getNbLevels() ); } /* * Display the ranges (debugging) */ void CPairSelector::printRanges( bool checkIntegrity ) const { TRanges::const_iterator irange; for ( irange=_Ranges.begin(); irange!=_Ranges.end(); ++irange ) { nlinfo( "FEPRIO: Range %u: %u elements, threshold %d", (*irange).IndexInRanges, (*irange).NumberOfElements, (*irange).DistThreshold ); } if ( ! _Ranges.empty() ) { nlinfo( "FEPRIO: Total pairs: %u", nbPairs() ); if ( checkIntegrity ) { nlassert( lastRange().beginIndex( _Ranges ) + lastRange().NumberOfElements == nbPairs() ); } } } /* * Display the pairs (debugging) */ void CPairSelector::printPairs() const { TPairVector::const_iterator ipv; // stringstream ss; // ss << endl; string str = "\n"; sint i = 0; for ( ipv=_PairsByDistance.begin(); ipv!=_PairsByDistance.end(); ++ipv ) { //ss << i << ": Client " << (*ipv).ClientId << " - Slot " << (*ipv).CeId << " - Distance " << _VisionArray->distanceCE( (*ipv).ClientId,(*ipv).CeId ) << endl; str += NLMISC::toString(i) + ": Client " + NLMISC::toString((*ipv).ClientId) + " - Slot " + NLMISC::toString((*ipv).CeId) + " - Distance " + NLMISC::toString(_VisionArray->distanceCE( (*ipv).ClientId,(*ipv).CeId )) + "\n"; ++i; } nlinfo( "%s", str.c_str() ); } /* * Print the scores if there is a scoring selection generator */ void CPairSelector::printScores() const { CScoringGenerator* sg = dynamic_cast(_SelGenerator); if ( sg ) sg->printScores(); else nlwarning( "No scoring generator engaged" ); } NLMISC_COMMAND( displayPairSelectorRanges, "Print the ranges of the pair selector", "[]" ) { bool check = false; if ( (args.size() > 0) && (atoi(args[0].c_str()) != 0) ) { check = true; } CFrontEndService::instance()->PrioSub.PairSelector.printRanges( check, &log ); return true; } NLMISC_COMMAND( displayPairSelector, "Print the pairs sorted by distance", "" ) { CFrontEndService::instance()->PrioSub.PairSelector.printPairs(&log); return true; } NLMISC_COMMAND( displayGeneratorScores, "Print the scores of the scoring generator", "" ) { CFrontEndService::instance()->PrioSub.PairSelector.printScores(&log); return true; } NLMISC_COMMAND( setNbpairsPerRange, "Set the number of pairs per range in the selector", "" ) { // check args, if there s not the right number of parameter, return bad if(args.size() != 1) return false; // get the values TPairIndex nb = atoi(args[0].c_str()); CFrontEndService::instance()->PrioSub.PairSelector.NbPairsPerRange = nb; return true; } NLMISC_COMMAND( getNbpairsPerRange, "Get the number of pairs per range in the selector", "" ) { log.displayNL( "%d", CFrontEndService::instance()->PrioSub.PairSelector.NbPairsPerRange ); return true; } NLMISC_COMMAND( setMinNbpairsToSelect, "Set the minimum number of pairs to select", "" ) { // check args, if there s not the right number of parameter, return bad if(args.size() != 1) return false; // get the values TPairIndex nb = atoi(args[0].c_str()); CFrontEndService::instance()->PrioSub.PairSelector.MinNbPairsToSelect = nb; return true; } NLMISC_COMMAND( getNbpairsToSelect, "Get the number of pairs to select and the minium", "" ) { log.displayNL( "min=%d current=%d", CFrontEndService::instance()->PrioSub.PairSelector.MinNbPairsToSelect, CFrontEndService::instance()->PrioSub.PairSelector.NbPairsToSelect ); return true; } NLMISC_DYNVARIABLE( uint32, NbPairs, "Number of pairs" ) { // We can only read the value if ( get ) *pointer = CFrontEndService::instance()->PrioSub.PairSelector.nbPairs(); }