// 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 "prioritizer.h" #include "vision_array.h" #include "property_dispatcher.h" #include "property_id_translator.h" #include "history.h" #include "frontend_service.h" #include "fe_stat.h" using namespace CLFECOMMON; /* Threshold for Distance/Delta. * Ex: RATIO_MAX_THRESHOLD = 2 * * Delta * |--------|--------| * D|L.......H......H/ * i| / * s| / * t| / * a| / * n| / * c|45° / * e| / * |/ * ° * Observer * * If Delta exceeds Distance/2, the priority shelf (bucket) is the highest one (H) * Else the shelves cover linearly the length between 0 and Distance/2. * * Note: the highest priority is 0, the lowest is NB_PRIORITIES-1. * */ const sint RATIO_MAX_THRESHOLD = 2; // Deprecated /* const TCoord CPrioritizer::DeltaMinThreshold = 200; // 20 cm (all smaller deltas will trigger min priority) const TCoord CPrioritizer::DeltaMaxThreshold = 12000; // 12 m (all higher deltas will trigger max priority) const TCoord CPrioritizer::DistMinThreshold = 500; // 50 cm (all nearer distances will trigger max priority if moving) const TCoord CPrioritizer::DistMaxThreshold = 600000; // 600 m (all further distances will trigger min priority) */ const TCoord CPrioritizer::MaxDelta = 2000000; // about 2000 m /* * Constructor */ CPrioritizer::CPrioritizer() : _PrioStrategy( DistanceDelta ), _VisionArray( NULL ), _PropTranslator( NULL ), _History( NULL ) { } /* * Initialization */ void CPrioritizer::init( CVisionArray *va, CPropertyIdTranslator* pt, CHistory *h ) { _VisionArray = va; _PropTranslator = pt; _History = h; PositionSpreader.init(); OrientationSpreader.init(); DiscreetSpreader.init(); } /* * Set the priority strategy */ /*void CPrioritizer::setPrioStrategy( TPrioStrategy ps ) { _PrioStrategy = ps; switch( _PrioStrategy ) { case DistanceOnly: MaxRatio = (float)DistMaxThreshold; break; case DistanceDelta: MaxRatio = DistMaxThreshold / DeltaMinThreshold; // distance/delta, const max break; default: nlwarning( "Invalid priority strategy set, resetting to DistanceDelta" ); setPrioStrategy( DistanceDelta ); } }*/ /* * Calculate the priorities for the current cycle */ void CPrioritizer::calculatePriorities() { /*switch( _PrioStrategy ) { case DistanceOnly: calculatePrioDistance(); break; case DistanceDelta:*/ calculatePriorityOfPosition(); calculatePriorityOfOrientation(); calculatePriorityOfDiscreet(); /* break; default: nlstop; }*/ } /* * Calculate using strategy DistanceOnly */ /*void CPrioritizer::calculatePrioDistance() { // Process the pairs selected by the Pair Selector uint32 uprio; // using uint32 because uint8 can be too short in some cases const TPairCE *pairCE; while ( (pairCE = _PairSelector->selectNextPair()) != NULL ) { CVisionArray::TVAItem& item = _VisionArray->vaItem( pairCE->ClientId, pairCE->CeId ); // Discard if client has left if ( item.DistanceCE != DISCARD_PAIR ) { // For each property corresponding to the entity type TPropIndex p; for ( p=0; p<_PropTranslator->nbProperties( item.EntityIndex ); ++p ) { // Calculate priority only if the update status is ToUpdate, // meaning that a change has not been sent to the client (especially // for a discreet property ; for a continuous property the status // is always ToUpdate) // if ( item.Properties[p].UpdateStatus == ToUpdate ) { CClientHost *clienthost = CFrontEndService::instance()->sendSub()->clientIdCont()[pairCE->ClientId]; //nlassertex( clienthost, ("c%hu", clientid) ); float hpthreshold = clienthost->PropDispatcher.hpThreshold(); // Clamp distance so that very near entities get the highest priority if ( (float)(item.DistanceCE) < hpthreshold ) { uprio = HIGHEST_PRIORITY; } else { // Linear priority, excluding HIGHEST_PRIORITY nlassert( MaxRatio > hpthreshold ); uprio = (uint32)((((float)item.DistanceCE-hpthreshold) * (float)(NB_PRIORITIES-2) / (MaxRatio-hpthreshold)) + 1.0f); nlassert( uprio > HIGHEST_PRIORITY ); // Clamp priority if ( uprio >= NB_PRIORITIES ) { uprio = NB_PRIORITIES - 1; } } // Set the priority of the pair clienthost->PropDispatcher.setPriority( pairCE->CeId, p, _VisionArray->prioLoc( pairCE->ClientId, pairCE->CeId, p ), (TPriority)uprio ); } } } } }*/ /* * Calculate position priorities using strategy DistanceDelta */ void CPrioritizer::calculatePriorityOfPosition() { if ( PositionSpreader.mustProcessNow() ) { #ifdef MEASURE_FRONTEND_TABLES DistCntFrame.setGameTick(); DeltaCntFrame.setGameTick(); PrioCntFrame.setGameTick(); DistCntFrame.reset( -1 ); DeltaCntFrame.reset( -1 ); PrioCntFrame.reset( -1 ); #endif THostMap::iterator icm; sint clientmapindex, outerBoundIndex; PositionSpreader.getProcessingBounds( icm, clientmapindex, outerBoundIndex ); sint bucket; while ( clientmapindex < outerBoundIndex ) { CClientHost *clienthost = GETCLIENTA(icm); TVAProp tvaPos = _VisionArray->tvaProp( clienthost->clientId(), PROPERTY_POSITION ); for ( sint e=0; e!=MAX_SEEN_ENTITIES_PER_CLIENT; ++e ) { // Calculate priority only if the update status is ToUpdate (even for a continuous prop) if ( tvaPos[e].PropState.UpdateStatus == ToUpdate ) { TEntityIndex entityIndex = _VisionArray->getEntityIndex( clienthost->clientId(), (TCLEntityId)e ); if ( entityIndex.isValid() ) { // Get delta TCoord delta = getDeltaPos( entityIndex, clienthost->clientId(), e ); #ifdef MEASURE_FRONTEND_TABLES if ( clienthost->clientId() == 1 ) { DistCntFrame.SeenEntities[e] = distanceCE; DeltaCntFrame.SeenEntities[e] = delta; } #endif // Do not set priority if delta is zero (steady entities...) if ( delta != 0 ) { TCoord distanceCE = _VisionArray->distanceCE( clienthost->clientId(), (TCLEntityId)e ); float prioRatio = (float)distanceCE / (float)delta; // See explanation of formula on top of this file if ( prioRatio < RATIO_MAX_THRESHOLD ) { bucket = HIGHEST_PRIORITY; } else { bucket = LOWEST_PRIORITY - (sint)((float)(LOWEST_PRIORITY*RATIO_MAX_THRESHOLD) / prioRatio); } #ifdef MEASURE_FRONTEND_TABLES if ( clienthost->clientId() == 1 ) { PrioCntFrame.SeenEntities[e] = bucket; } #endif // Set the priority of the pair clienthost->PropDispatcher.setPriority( e, PROPERTY_POSITION, tvaPos[e].PropState, (TPriority)bucket ); /*nlinfo( "FEPRIO: Set cep %hu-%hu-%hu to priority %u", (uint16)pairCE->ClientId, (uint16)pairCE->CeId, (uint16)p, uprio );*/ } } } } ++clientmapindex; ++icm; } PositionSpreader.endProcessing( icm ); #ifdef MEASURE_FRONTEND_TABLES DistCntFrame.commit( DistCntClt1 ); DeltaCntFrame.commit( DeltaCntClt1 ); PrioCntFrame.commit( PrioCntClt1 ); #endif } PositionSpreader.incCycle(); } /* * Calculate orientation priorities using strategy DistanceDelta */ void CPrioritizer::calculatePriorityOfOrientation() { if ( OrientationSpreader.mustProcessNow() ) { THostMap::iterator icm; sint clientmapindex, outerBoundIndex; OrientationSpreader.getProcessingBounds( icm, clientmapindex, outerBoundIndex ); sint bucket; while ( clientmapindex < outerBoundIndex ) { CClientHost *clienthost = GETCLIENTA(icm); TVAProp tvaOrt = _VisionArray->tvaProp( clienthost->clientId(), PROPERTY_ORIENTATION ); for ( sint e=0; e!=MAX_SEEN_ENTITIES_PER_CLIENT; ++e ) { // Calculate priority only if the update status is ToUpdate (even for a continuous prop) if ( tvaOrt[e].PropState.UpdateStatus == ToUpdate ) { TEntityIndex entityIndex = _VisionArray->getEntityIndex( clienthost->clientId(), (TCLEntityId)e ); if ( entityIndex.isValid() ) { // Get delta TCoord delta = getDeltaOrientation( entityIndex, clienthost->clientId(), e ); // Do not set priority if delta is zero (steady entities...) if ( delta != 0 ) { TCoord distanceCE = _VisionArray->distanceCE( clienthost->clientId(), (TCLEntityId)e ); float prioRatio = (float)distanceCE / (float)delta; // See explanation of formula on top of this file if ( prioRatio < RATIO_MAX_THRESHOLD ) { bucket = HIGHEST_PRIORITY; } else { bucket = LOWEST_PRIORITY - (sint)((float)(LOWEST_PRIORITY*RATIO_MAX_THRESHOLD) / prioRatio); } // Set the priority of the pair clienthost->PropDispatcher.setPriority( e, PROPERTY_ORIENTATION, tvaOrt[e].PropState, (TPriority)bucket ); /*nlinfo( "FEPRIO: Set cep %hu-%hu-%hu to priority %u", (uint16)pairCE->ClientId, (uint16)pairCE->CeId, (uint16)p, uprio );*/ } } } } ++clientmapindex; ++icm; } OrientationSpreader.endProcessing( icm ); } OrientationSpreader.incCycle(); } /* * Calculate discreet props priorities using distance threshold */ void CPrioritizer::calculatePriorityOfDiscreet() { if ( DiscreetSpreader.mustProcessNow() ) { THostMap::iterator icm; sint clientmapindex, outerBoundIndex; DiscreetSpreader.getProcessingBounds( icm, clientmapindex, outerBoundIndex ); sint bucket; while ( clientmapindex < outerBoundIndex ) { CClientHost *clienthost = GETCLIENTA(icm); for ( sint e=0; e!=MAX_SEEN_ENTITIES_PER_CLIENT; ++e ) { TEntityIndex entityIndex = _VisionArray->getEntityIndex( clienthost->clientId(), (TCLEntityId)e ); if ( entityIndex.isValid() ) { TCoord distanceCE = _VisionArray->distanceCE( clienthost->clientId(), (TCLEntityId)e ); // For each discreet property corresponding to the entity type for ( sint p=FIRST_DISCREET_PROPINDEX; p<_PropTranslator->nbProperties( entityIndex ); ++p ) { // Calculate priority only if the update status is ToUpdate, // meaning that a change has not been sent to the client // if ( _VisionArray->tvaProp( clienthost->clientId(), p )[e].PropState.UpdateStatus == ToUpdate ) { // Get TProperty from TPropIndex TProperty propertyid = _PropTranslator->getPropertyId( entityIndex, p ); if ( distanceCE < _PropTranslator->getDistThreshold( propertyid ) ) { bucket = HIGHEST_PRIORITY; } else { bucket = LOWEST_PRIORITY; } // Set the priority of the pair clienthost->PropDispatcher.setPriority( e, p, _VisionArray->prioLoc( clienthost->clientId(), e, p ), (TPriority)bucket ); //nlinfo( "FEPRIO: Set cep %hu-%hu-%hu to priority %u", // (uint16)pairCE->ClientId, (uint16)pairCE->CeId, (uint16)p, uprio ); } } } } ++clientmapindex; ++icm; } DiscreetSpreader.endProcessing( icm ); } DiscreetSpreader.incCycle(); } bool TraceDelta = true; /* * Return the delta corresponding to a property */ inline TCoord CPrioritizer::getDeltaPos( TEntityIndex entityindex, TClientId clientid, TCLEntityId ceid ) { // Position special case uint32 lastSentMileage = _History->getMileage( clientid, ceid ); if ( lastSentMileage != 0 ) { CFrontEndPropertyReceiver::SEntity *entity = CFrontEndPropertyReceiver::getEntity( entityindex ); // Calculate difference distance between current and lastsent mileage (unsigned to allow mileage overflow) uint32 d = (entity->Mileage - lastSentMileage);//(uint32)frand(100000.0); // Ignore not significant deltas (maybe not needed anymore with integer calculation) if ( d < 5 ) { //nlinfo( "Low position delta = %u", d ); return 0; } else { /*if ( TraceDelta ) nlinfo( "%d", d );*/ return (TCoord)d; } } else { // Not sent yet, set max delta //nlinfo( "FEPRIO: getDelta(position) : first time" ); return MaxDelta; } //nlinfo( "Delta: %.2f (current: %s last: %s)", delta, current_entitypos.asString().c_str(), lastsent_entitypos.asString().c_str() ); } inline TCoord CPrioritizer::getDeltaOrientation( TEntityIndex entityindex, TClientId clientid, TCLEntityId ceid ) { bool histohasvalue; const CFrontEndPropertyReceiver::TPropertiesValue current_value = CFrontEndPropertyReceiver::getEntity( entityindex )->properties[PROPERTY_ORIENTATION]; const CFrontEndPropertyReceiver::TPropertiesValue& lastsent_value = _History->getPropertyEntry( clientid, ceid, PROPERTY_ORIENTATION, histohasvalue ).LastSent; if ( histohasvalue ) { // Orientation is a float angle in radian const float& newangle = *((float*)¤t_value);//frand(6.28); const float& oldangle = *((float*)&lastsent_value); float deltaAngle = (float)fabs( (float)(newangle - oldangle) ); deltaAngle = (float)fmod( deltaAngle+(2*Pi), (2*Pi) ); // Delta=1 m corresponds to Pi //nlinfo( "getDelta(theta) : dA=%g", deltaAngle ); if ( deltaAngle > Pi ) return (TCoord)(2000.0f - (deltaAngle * 1000.0f / Pi)); else return (TCoord)(deltaAngle * 1000.0f / Pi); } else { // Not sent yet, set max delta return MaxDelta; } } NLMISC_COMMAND( forcePriority, "Force a priority", " " ) { // check args, if there s not the right number of parameter, return bad if(args.size() != 4) return false; // get the values TClientId clientid = atoi(args[0].c_str()); sint slot = atoi(args[1].c_str()); TPropIndex propindex = atoi(args[2].c_str()); TPriority priority = atoi(args[3].c_str()); if ( (clientid <= MaxNbClients) && (slot < MAX_SEEN_ENTITIES_PER_CLIENT) && (propindex < NB_VISUAL_PROPERTIES/*16*/) && ( priority < NB_PRIORITIES ) ) { CClientHost *clienthost = CFrontEndService::instance()->sendSub()->clientIdCont()[clientid]; //nlassertex( clienthost, ("c%hu", clientid) ); clienthost->PropDispatcher.setPriority( slot, propindex, CFrontEndService::instance()->PrioSub.VisionArray.prioLoc( clientid, slot, propindex ), priority ); } else { log.displayNL( "Invalid argument value(s)" ); } return true; } /*NLMISC_COMMAND( setPriorityStrategy, "Change the priority strategy, distance-delta or distance only", "1/0" ) { // check args, if there s not the right number of parameter, return bad if(args.size() != 1) return false; // get the values sint strat = atoi(args[0].c_str()); switch( strat ) { case 0: CFrontEndService::instance()->PrioSub.Prioritizer.setPrioStrategy( CPrioritizer::DistanceOnly ); break; case 1: CFrontEndService::instance()->PrioSub.Prioritizer.setPrioStrategy( CPrioritizer::DistanceDelta ); break; default: return false; } THostMap::iterator ihm; for ( ihm=CFrontEndService::instance()->receiveSub()->clientMap().begin(); ihm!=CFrontEndService::instance()->receiveSub()->clientMap().end(); ++ihm ) { GETCLIENTA(ihm)->PropDispatcher.resetThreshold(); } return true; } NLMISC_COMMAND( getPriorityStrategy, "Get the priority strategy", "" ) { switch( CFrontEndService::instance()->PrioSub.Prioritizer.prioStrategy() ) { case CPrioritizer::DistanceOnly: log.displayNL( "Distance only" ); break; case CPrioritizer::DistanceDelta: log.displayNL( "Distance-delta" ); break; } return true; } NLMISC_COMMAND( getPriorityThresholds, "Get the threshold constants", "" ) { log.displayNL( "MinDelta=%d MaxDelta=%d MinDist=%d MaxDist=%d", CPrioritizer::DeltaMinThreshold, CPrioritizer::DeltaMaxThreshold, CPrioritizer::DistMinThreshold, CPrioritizer::DistMaxThreshold ); return true; }*/ NLMISC_COMMAND( getDelta, "Get the delta for a priority", " " ) { // check args, if there s not the right number of parameter, return bad if(args.size() != 3) return false; // get the values TClientId clientid = atoi(args[0].c_str()); sint slot = atoi(args[1].c_str()); TPropIndex propindex = atoi(args[2].c_str()); if ( (clientid <= MaxNbClients) && (CFrontEndService::instance()->sendSub()->clientIdCont()[clientid] != NULL) ) { if ( slot < MAX_SEEN_ENTITIES_PER_CLIENT ) { TEntityIndex entityindex = CFrontEndService::instance()->PrioSub.VisionArray.getEntityIndex( clientid, (TCLEntityId)slot ); if ( propindex > 15 ) return false; TProperty propertyid = CFrontEndService::instance()->PrioSub.PropTranslator.getPropertyId( entityindex, propindex ); if ( propertyid == PROPERTY_POSITION ) log.displayNL( "%d", CFrontEndService::instance()->PrioSub.Prioritizer.getDeltaPos( entityindex, clientid, (TCLEntityId)slot ) ); else if ( propertyid == PROPERTY_ORIENTATION ) log.displayNL( "%d", CFrontEndService::instance()->PrioSub.Prioritizer.getDeltaOrientation( entityindex, clientid, (TCLEntityId)slot ) ); else log.displayNL( "Invalid property id" ); } else { log.displayNL( "Invalid slot" ); } } else { log.displayNL( "There is no such a client id" ); } return true; } /*NLMISC_COMMAND( delta, "Trace delta", "0/1" ) { if ( args.size() == 0 ) return false; TraceDelta = ( atoi(args[0].c_str()) == 1 ); return true; }*/