[pal] Provide more exit points for early cancelation

When a rendering operation was canceled, PAL had very few early
exit points. This often resulted in many canceled rendering operations
burning away in background threads as labeling candidates and solutions
were being generated for jobs which were no longer needed.

Add more exit points and cancel checks throughout various expensive
pal operations, allowing labeling jobs to terminate quickly.

Fixes #32489
This commit is contained in:
Nyall Dawson 2019-11-29 10:09:38 +10:00
parent 8878e9b30b
commit 09b8612761
5 changed files with 91 additions and 25 deletions

View File

@ -649,13 +649,13 @@ QList< LegendLayerAction > QgsAppLayerTreeViewMenuProvider::legendLayerActions(
#ifdef QGISDEBUG
if ( mLegendLayerActionMap.contains( type ) )
{
QgsDebugMsg( QStringLiteral( "legendLayerActions for layers of type %1:" ).arg( static_cast<int>( type ) ) );
QgsDebugMsgLevel( QStringLiteral( "legendLayerActions for layers of type %1:" ).arg( static_cast<int>( type ) ), 2 );
const auto legendLayerActions { mLegendLayerActionMap.value( type ) };
for ( const LegendLayerAction &lyrAction : legendLayerActions )
{
Q_UNUSED( lyrAction )
QgsDebugMsg( QStringLiteral( "%1/%2 - %3 layers" ).arg( lyrAction.menu, lyrAction.action->text() ).arg( lyrAction.layers.count() ) );
QgsDebugMsgLevel( QStringLiteral( "%1/%2 - %3 layers" ).arg( lyrAction.menu, lyrAction.action->text() ).arg( lyrAction.layers.count() ), 2 );
}
}
#endif

View File

@ -617,7 +617,7 @@ std::size_t FeaturePart::createCandidatesAroundPoint( double x, double y, std::v
return numberCandidatesGenerated;
}
std::size_t FeaturePart::createCandidatesAlongLine( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun )
std::size_t FeaturePart::createCandidatesAlongLine( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal )
{
if ( allowOverrun )
{
@ -632,17 +632,17 @@ std::size_t FeaturePart::createCandidatesAlongLine( std::vector< std::unique_ptr
}
//prefer to label along straightish segments:
std::size_t candidates = createCandidatesAlongLineNearStraightSegments( lPos, mapShape );
std::size_t candidates = createCandidatesAlongLineNearStraightSegments( lPos, mapShape, pal );
if ( static_cast< int >( candidates ) < mLF->layer()->maximumLineLabelCandidates() )
{
// but not enough candidates yet, so fallback to labeling near whole line's midpoint
candidates = createCandidatesAlongLineNearMidpoint( lPos, mapShape, candidates > 0 ? 0.01 : 0.0 );
candidates = createCandidatesAlongLineNearMidpoint( lPos, mapShape, candidates > 0 ? 0.01 : 0.0, pal );
}
return candidates;
}
std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape )
std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal )
{
double labelWidth = getLabelWidth();
double labelHeight = getLabelHeight();
@ -765,6 +765,11 @@ std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vec
while ( currentDistanceAlongLine + labelWidth < distanceToEndOfSegment )
{
if ( pal->isCanceled() )
{
return lPos.size();
}
// calculate positions along linestring corresponding to start and end of current label candidate
line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
@ -862,7 +867,7 @@ std::size_t FeaturePart::createCandidatesAlongLineNearStraightSegments( std::vec
return lPos.size();
}
std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, double initialCost )
std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, double initialCost, Pal *pal )
{
double distanceLineToLabel = getLabelDistance();
@ -922,6 +927,11 @@ std::size_t FeaturePart::createCandidatesAlongLineNearMidpoint( std::vector< std
int i = 0;
while ( currentDistanceAlongLine < totalLineLength - labelWidth )
{
if ( pal->isCanceled() )
{
return lPos.size();
}
// calculate positions along linestring corresponding to start and end of current label candidate
line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine, &candidateStartX, &candidateStartY );
line->getPointByDistance( segmentLengths.data(), distanceToSegment.data(), currentDistanceAlongLine + labelWidth, &candidateEndX, &candidateEndY );
@ -1193,7 +1203,7 @@ static LabelPosition *_createCurvedCandidate( LabelPosition *lp, double angle, d
return newLp;
}
std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun )
std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal )
{
LabelInfo *li = mLF->curvedLabelInfo();
@ -1257,6 +1267,9 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
return 0;
}
if ( pal->isCanceled() )
return 0;
QLinkedList<LabelPosition *> positions;
double delta = std::max( li->label_height / 6, total_distance / mLF->layer()->maximumLineLabelCandidates() );
@ -1271,6 +1284,9 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
// placements may need to be reversed if using map orientation and the line has right-to-left direction
bool reversed = false;
if ( pal->isCanceled() )
return 0;
// an orientation of 0 means try both orientations and choose the best
int orientation = 0;
if ( !( flags & FLAG_MAP_ORIENTATION ) )
@ -1393,7 +1409,7 @@ std::size_t FeaturePart::createCurvedCandidatesAlongLine( std::vector< std::uniq
*
*/
std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape )
std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_ptr< LabelPosition > > &lPos, PointSet *mapShape, Pal *pal )
{
double labelWidth = getLabelWidth();
double labelHeight = getLabelHeight();
@ -1403,6 +1419,9 @@ std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_pt
mapShape->parent = nullptr;
if ( pal->isCanceled() )
return 0;
shapes_toProcess.append( mapShape );
splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight );
@ -1433,6 +1452,9 @@ std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_pt
delete shape;
}
if ( pal->isCanceled() )
return 0;
//dx = dy = min( yrm, xrm ) / 2;
dx = labelWidth / 2.0;
dy = labelHeight / 2.0;
@ -1449,6 +1471,9 @@ std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_pt
{
for ( CHullBox &box : boxes )
{
if ( pal->isCanceled() )
return numberCandidatesGenerated;
if ( ( box.length * box.width ) > ( xmax - xmin ) * ( ymax - ymin ) * 5 )
{
// Very Large BBOX (should never occur)
@ -1538,6 +1563,9 @@ std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_pt
for ( px = px0; px <= box.width; px += dx )
{
if ( pal->isCanceled() )
break;
for ( py = py0; py <= box.length; py += dy )
{
@ -1580,7 +1608,7 @@ std::size_t FeaturePart::createCandidatesForPolygon( std::vector< std::unique_pt
return nbp;
}
std::vector< std::unique_ptr< LabelPosition > > FeaturePart::createCandidates()
std::vector< std::unique_ptr< LabelPosition > > FeaturePart::createCandidates( Pal *pal )
{
std::vector< std::unique_ptr< LabelPosition > > lPos;
double angle = mLF->hasFixedAngle() ? mLF->fixedAngle() : 0.0;
@ -1603,9 +1631,9 @@ std::vector< std::unique_ptr< LabelPosition > > FeaturePart::createCandidates()
break;
case GEOS_LINESTRING:
if ( mLF->layer()->isCurved() )
createCurvedCandidatesAlongLine( lPos, this, true );
createCurvedCandidatesAlongLine( lPos, this, true, pal );
else
createCandidatesAlongLine( lPos, this, true );
createCandidatesAlongLine( lPos, this, true, pal );
break;
case GEOS_POLYGON:
@ -1621,13 +1649,13 @@ std::vector< std::unique_ptr< LabelPosition > > FeaturePart::createCandidates()
createCandidatesAroundPoint( cx, cy, lPos, angle );
break;
case QgsPalLayerSettings::Line:
createCandidatesAlongLine( lPos, this );
createCandidatesAlongLine( lPos, this, false, pal );
break;
case QgsPalLayerSettings::PerimeterCurved:
createCurvedCandidatesAlongLine( lPos, this );
createCurvedCandidatesAlongLine( lPos, this, false, pal );
break;
default:
createCandidatesForPolygon( lPos, this );
createCandidatesForPolygon( lPos, this, pal );
break;
}
}

View File

@ -130,7 +130,7 @@ namespace pal
/**
* Generates a list of candidate positions for labels for this feature.
*/
std::vector<std::unique_ptr<LabelPosition> > createCandidates();
std::vector<std::unique_ptr<LabelPosition> > createCandidates( Pal *pal );
/**
* Generate candidates for point feature, located around a specified point.
@ -175,18 +175,20 @@ namespace pal
* \param lPos pointer to an array of candidates, will be filled by generated candidates
* \param mapShape a pointer to the line
* \param allowOverrun set to TRUE to allow labels to overrun features
* \param pal point to pal settings object, for cancellation support
* \returns the number of generated candidates
*/
std::size_t createCandidatesAlongLine( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, bool allowOverrun = false );
std::size_t createCandidatesAlongLine( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal );
/**
* Generate candidates for line feature, by trying to place candidates towards the middle of the longest
* straightish segments of the line. Segments closer to horizontal are preferred over vertical segments.
* \param lPos pointer to an array of candidates, will be filled by generated candidates
* \param mapShape a pointer to the line
* \param pal point to pal settings object, for cancellation support
* \returns the number of generated candidates
*/
std::size_t createCandidatesAlongLineNearStraightSegments( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape );
std::size_t createCandidatesAlongLineNearStraightSegments( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, Pal *pal );
/**
* Generate candidates for line feature, by trying to place candidates as close as possible to the line's midpoint.
@ -197,7 +199,7 @@ namespace pal
* by a preset amount.
* \returns the number of generated candidates
*/
std::size_t createCandidatesAlongLineNearMidpoint( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, double initialCost = 0.0 );
std::size_t createCandidatesAlongLineNearMidpoint( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, double initialCost = 0.0, Pal *pal = nullptr );
/**
* Returns the label position for a curved label at a specific offset along a path.
@ -217,17 +219,19 @@ namespace pal
* \param lPos pointer to an array of candidates, will be filled by generated candidates
* \param mapShape a pointer to the line
* \param allowOverrun set to TRUE to allow labels to overrun features
* \param pal point to pal settings object, for cancellation support
* \returns the number of generated candidates
*/
std::size_t createCurvedCandidatesAlongLine( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, bool allowOverrun = false );
std::size_t createCurvedCandidatesAlongLine( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, bool allowOverrun, Pal *pal );
/**
* Generate candidates for polygon features.
* \param lPos pointer to an array of candidates, will be filled by generated candidates
* \param mapShape a pointer to the polygon
* \param pal point to pal settings object, for cancellation support
* \returns the number of generated candidates
*/
std::size_t createCandidatesForPolygon( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape );
std::size_t createCandidatesForPolygon( std::vector<std::unique_ptr<LabelPosition> > &lPos, PointSet *mapShape, Pal *pal );
/**
* Tests whether this feature part belongs to the same QgsLabelFeature as another

View File

@ -119,6 +119,9 @@ bool extractFeatCallback( FeaturePart *featurePart, void *ctx )
double amin[2], amax[2];
FeatCallBackCtx *context = reinterpret_cast< FeatCallBackCtx * >( ctx );
if ( context->pal->isCanceled() )
return false;
// Holes of the feature are obstacles
for ( int i = 0; i < featurePart->getNumSelfObstacles(); i++ )
{
@ -132,7 +135,10 @@ bool extractFeatCallback( FeaturePart *featurePart, void *ctx )
}
// generate candidates for the feature part
std::vector< std::unique_ptr< LabelPosition > > candidates = featurePart->createCandidates();
std::vector< std::unique_ptr< LabelPosition > > candidates = featurePart->createCandidates( context->pal );
if ( context->pal->isCanceled() )
return false;
// purge candidates that are outside the bbox
candidates.erase( std::remove_if( candidates.begin(), candidates.end(), [&context]( std::unique_ptr< LabelPosition > &candidate )
@ -143,6 +149,9 @@ bool extractFeatCallback( FeaturePart *featurePart, void *ctx )
return !candidate->within( context->mapBoundary );
} ), candidates.end() );
if ( context->pal->isCanceled() )
return false;
if ( !candidates.empty() )
{
for ( std::unique_ptr< LabelPosition > &candidate : candidates )
@ -175,6 +184,7 @@ struct ObstacleCallBackCtx
{
RTree<FeaturePart *, double, 2, double> *obstacleIndex = nullptr;
int obstacleCount = 0;
Pal *pal = nullptr;
};
/*
@ -186,6 +196,8 @@ bool extractObstaclesCallback( FeaturePart *ft_ptr, void *ctx )
{
double amin[2], amax[2];
ObstacleCallBackCtx *context = reinterpret_cast< ObstacleCallBackCtx * >( ctx );
if ( context->pal->isCanceled() )
return false; // do not continue searching
// insert into obstacles
ft_ptr->getBoundingBox( amin, amax );
@ -258,6 +270,7 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
ObstacleCallBackCtx obstacleContext;
obstacleContext.obstacleIndex = &obstacles;
obstacleContext.obstacleCount = 0;
obstacleContext.pal = this;
// first step : extract features from layers
@ -284,15 +297,26 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
if ( layer->mergeConnectedLines() )
layer->joinConnectedFeatures();
if ( isCanceled() )
return nullptr;
layer->chopFeaturesAtRepeatDistance();
if ( isCanceled() )
return nullptr;
QMutexLocker locker( &layer->mMutex );
// find features within bounding box and generate candidates list
context.layer = layer;
layer->mFeatureIndex.Search( amin, amax, extractFeatCallback, static_cast< void * >( &context ) );
if ( isCanceled() )
return nullptr;
// find obstacles within bounding box
layer->mObstacleIndex.Search( amin, amax, extractObstaclesCallback, static_cast< void * >( &obstacleContext ) );
if ( isCanceled() )
return nullptr;
locker.unlock();
@ -305,6 +329,9 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
}
palLocker.unlock();
if ( isCanceled() )
return nullptr;
prob->mLayerCount = layersWithFeaturesInBBox.size();
prob->labelledLayersName = layersWithFeaturesInBBox;
@ -354,6 +381,9 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
// sort candidates by cost, skip less interesting ones, calculate polygon costs (if using polygons)
max_p = CostCalculator::finalizeCandidatesCosts( feat.get(), max_p, &obstacles, bbx, bby );
if ( isCanceled() )
return nullptr;
// only keep the 'max_p' best candidates
while ( feat->candidates.size() > max_p )
{
@ -362,6 +392,9 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
feat->candidates.pop_back();
}
if ( isCanceled() )
return nullptr;
// update problem's # candidate
prob->mFeatNbLp[i] = static_cast< int >( feat->candidates.size() );
prob->mTotalCandidates += static_cast< int >( feat->candidates.size() );
@ -380,9 +413,7 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
while ( !features.empty() ) // foreach feature
{
if ( isCanceled() )
{
return nullptr;
}
std::unique_ptr< Feats > feat = std::move( features.front() );
features.pop_front();
@ -406,6 +437,9 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
nbOverlaps += lp->getNumOverlaps();
prob->addCandidatePosition( std::move( lp ) );
if ( isCanceled() )
return nullptr;
}
}
nbOverlaps /= 2;

View File

@ -309,7 +309,7 @@ void QgsVectorLayerRenderer::drawRenderer( QgsFeatureIterator &fit )
{
if ( context.renderingStopped() )
{
QgsDebugMsg( QStringLiteral( "Drawing of vector layer %1 canceled." ).arg( layerId() ) );
QgsDebugMsgLevel( QStringLiteral( "Drawing of vector layer %1 canceled." ).arg( layerId() ), 2 );
break;
}