From 09b8612761f3229739f34166b3b9d14cb684d158 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 29 Nov 2019 10:09:38 +1000 Subject: [PATCH] [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 --- src/app/qgsapplayertreeviewmenuprovider.cpp | 4 +- src/core/pal/feature.cpp | 54 ++++++++++++++++----- src/core/pal/feature.h | 16 +++--- src/core/pal/pal.cpp | 40 +++++++++++++-- src/core/qgsvectorlayerrenderer.cpp | 2 +- 5 files changed, 91 insertions(+), 25 deletions(-) diff --git a/src/app/qgsapplayertreeviewmenuprovider.cpp b/src/app/qgsapplayertreeviewmenuprovider.cpp index 8fde26006ac..735c2b19d20 100644 --- a/src/app/qgsapplayertreeviewmenuprovider.cpp +++ b/src/app/qgsapplayertreeviewmenuprovider.cpp @@ -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( type ) ) ); + QgsDebugMsgLevel( QStringLiteral( "legendLayerActions for layers of type %1:" ).arg( static_cast( 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 diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp index b4178712efe..204c471842a 100644 --- a/src/core/pal/feature.cpp +++ b/src/core/pal/feature.cpp @@ -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 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; } } diff --git a/src/core/pal/feature.h b/src/core/pal/feature.h index 7553d68cfc4..2920dfb9889 100644 --- a/src/core/pal/feature.h +++ b/src/core/pal/feature.h @@ -130,7 +130,7 @@ namespace pal /** * Generates a list of candidate positions for labels for this feature. */ - std::vector > createCandidates(); + std::vector > 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 > &lPos, PointSet *mapShape, bool allowOverrun = false ); + std::size_t createCandidatesAlongLine( std::vector > &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 > &lPos, PointSet *mapShape ); + std::size_t createCandidatesAlongLineNearStraightSegments( std::vector > &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 > &lPos, PointSet *mapShape, double initialCost = 0.0 ); + std::size_t createCandidatesAlongLineNearMidpoint( std::vector > &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 > &lPos, PointSet *mapShape, bool allowOverrun = false ); + std::size_t createCurvedCandidatesAlongLine( std::vector > &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 > &lPos, PointSet *mapShape ); + std::size_t createCandidatesForPolygon( std::vector > &lPos, PointSet *mapShape, Pal *pal ); /** * Tests whether this feature part belongs to the same QgsLabelFeature as another diff --git a/src/core/pal/pal.cpp b/src/core/pal/pal.cpp index be9e6e90ee6..200ebbbcf38 100644 --- a/src/core/pal/pal.cpp +++ b/src/core/pal/pal.cpp @@ -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 *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 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 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 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 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 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 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 Pal::extract( const QgsRectangle &extent, const QgsGeom nbOverlaps += lp->getNumOverlaps(); prob->addCandidatePosition( std::move( lp ) ); + + if ( isCanceled() ) + return nullptr; } } nbOverlaps /= 2; diff --git a/src/core/qgsvectorlayerrenderer.cpp b/src/core/qgsvectorlayerrenderer.cpp index de09cf29bb8..4b9c191bf5d 100644 --- a/src/core/qgsvectorlayerrenderer.cpp +++ b/src/core/qgsvectorlayerrenderer.cpp @@ -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; }