Thread testTrackClearances

We are frequently testing thousands of track segments.  They can each be
uniquely tested in parallel with reporting and marking guarded by
mutexes.  This speeds up the DRC tests substantially

Fixes https://gitlab.com/kicad/code/kicad/-/issues/15466
This commit is contained in:
Seth Hillbrand 2023-09-20 19:50:16 -07:00
parent b2ef620ea6
commit f4afd7e363
3 changed files with 101 additions and 75 deletions

View File

@ -75,6 +75,7 @@ const wxString DRC_TEST_PROVIDER::GetDescription() const { return wxEmptyString;
void DRC_TEST_PROVIDER::reportViolation( std::shared_ptr<DRC_ITEM>& item, void DRC_TEST_PROVIDER::reportViolation( std::shared_ptr<DRC_ITEM>& item,
const VECTOR2I& aMarkerPos, int aMarkerLayer ) const VECTOR2I& aMarkerPos, int aMarkerLayer )
{ {
std::lock_guard<std::mutex> lock( m_statsMutex );
if( item->GetViolatingRule() ) if( item->GetViolatingRule() )
accountCheck( item->GetViolatingRule() ); accountCheck( item->GetViolatingRule() );

View File

@ -132,6 +132,7 @@ protected:
DRC_ENGINE* m_drcEngine; DRC_ENGINE* m_drcEngine;
std::unordered_map<const DRC_RULE*, int> m_stats; std::unordered_map<const DRC_RULE*, int> m_stats;
bool m_isRuleDriven = true; bool m_isRuleDriven = true;
std::mutex m_statsMutex;
}; };
#endif // DRC_TEST_PROVIDER__H #endif // DRC_TEST_PROVIDER__H

View File

@ -559,108 +559,132 @@ void DRC_TEST_PROVIDER_COPPER_CLEARANCE::testTrackClearances()
{ {
// This is the number of tests between 2 calls to the progress bar // This is the number of tests between 2 calls to the progress bar
const int progressDelta = 100; const int progressDelta = 100;
int ii = 0;
reportAux( wxT( "Testing %d tracks & vias..." ), m_board->Tracks().size() ); reportAux( wxT( "Testing %d tracks & vias..." ), m_board->Tracks().size() );
std::map<BOARD_ITEM*, int> freePadsUsageMap; std::map<BOARD_ITEM*, int> freePadsUsageMap;
std::unordered_map<PTR_PTR_CACHE_KEY, layers_checked> checkedPairs; std::unordered_map<PTR_PTR_CACHE_KEY, layers_checked> checkedPairs;
std::mutex checkedPairsMutex;
std::mutex freePadsUsageMapMutex;
std::atomic<int> tracks_checked( 0 );
LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() ); LSET boardCopperLayers = LSET::AllCuMask( m_board->GetCopperLayerCount() );
for( PCB_TRACK* track : m_board->Tracks() ) auto testTrack = [&]( const int start_idx, const int end_idx )
{ {
if( !reportProgress( ii++, m_board->Tracks().size(), progressDelta ) ) for( int trackIdx = start_idx; trackIdx < end_idx; ++trackIdx )
break;
for( PCB_LAYER_ID layer : LSET( track->GetLayerSet() & boardCopperLayers ).Seq() )
{ {
std::shared_ptr<SHAPE> trackShape = track->GetEffectiveShape( layer ); PCB_TRACK* track = m_board->Tracks()[trackIdx];
m_board->m_CopperItemRTreeCache->QueryColliding( track, layer, layer, for( PCB_LAYER_ID layer : LSET( track->GetLayerSet() & boardCopperLayers ).Seq() )
// Filter: {
[&]( BOARD_ITEM* other ) -> bool std::shared_ptr<SHAPE> trackShape = track->GetEffectiveShape( layer );
{
auto otherCItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( other );
if( otherCItem && otherCItem->GetNetCode() == track->GetNetCode() ) m_board->m_CopperItemRTreeCache->QueryColliding( track, layer, layer,
return false; // Filter:
[&]( BOARD_ITEM* other ) -> bool
BOARD_ITEM* a = track;
BOARD_ITEM* b = other;
// store canonical order so we don't collide in both directions
// (a:b and b:a)
if( static_cast<void*>( a ) > static_cast<void*>( b ) )
std::swap( a, b );
auto it = checkedPairs.find( { a, b } );
if( it != checkedPairs.end() && ( it->second.layers.test( layer )
|| ( it->second.has_error && !m_drcEngine->GetReportAllTrackErrors() ) ) )
{ {
return false; auto otherCItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( other );
}
else if( otherCItem && otherCItem->GetNetCode() == track->GetNetCode() )
{ return false;
checkedPairs[ { a, b } ].layers.set( layer );
return true; BOARD_ITEM* a = track;
} BOARD_ITEM* b = other;
},
// Visitor: // store canonical order so we don't collide in both directions
[&]( BOARD_ITEM* other ) -> bool // (a:b and b:a)
{ if( static_cast<void*>( a ) > static_cast<void*>( b ) )
if( other->Type() == PCB_PAD_T && static_cast<PAD*>( other )->IsFreePad() ) std::swap( a, b );
{
if( other->GetEffectiveShape( layer )->Collide( trackShape.get() ) ) std::lock_guard<std::mutex> lock( checkedPairsMutex );
auto it = checkedPairs.find( { a, b } );
if( it != checkedPairs.end() && ( it->second.layers.test( layer )
|| ( it->second.has_error && !m_drcEngine->GetReportAllTrackErrors() ) ) )
{ {
auto it = freePadsUsageMap.find( other ); return false;
}
else
{
checkedPairs[ { a, b } ].layers.set( layer );
return true;
}
},
// Visitor:
[&]( BOARD_ITEM* other ) -> bool
{
if( other->Type() == PCB_PAD_T && static_cast<PAD*>( other )->IsFreePad() )
{
if( other->GetEffectiveShape( layer )->Collide( trackShape.get() ) )
{
std::lock_guard<std::mutex> lock( freePadsUsageMapMutex );
auto it = freePadsUsageMap.find( other );
if( it == freePadsUsageMap.end() ) if( it == freePadsUsageMap.end() )
{ {
freePadsUsageMap[ other ] = track->GetNetCode(); freePadsUsageMap[ other ] = track->GetNetCode();
return false; return false;
} }
else if( it->second == track->GetNetCode() ) else if( it->second == track->GetNetCode() )
{ {
return false; return false;
}
} }
} }
}
BOARD_ITEM* a = track; BOARD_ITEM* a = track;
BOARD_ITEM* b = other; BOARD_ITEM* b = other;
// store canonical order so we don't collide in both directions // store canonical order so we don't collide in both directions
// (a:b and b:a) // (a:b and b:a)
if( static_cast<void*>( a ) > static_cast<void*>( b ) ) if( static_cast<void*>( a ) > static_cast<void*>( b ) )
std::swap( a, b ); std::swap( a, b );
auto it = checkedPairs.find( { a, b } ); // If we get an error, mark the pair as having a clearance error already
// Only continue if we are reporting all track errors
if( !testSingleLayerItemAgainstItem( track, trackShape.get(), layer,
other ) )
{
std::lock_guard<std::mutex> lock( checkedPairsMutex );
auto it = checkedPairs.find( { a, b } );
// If we get an error, mark the pair as having a clearance error already if( it != checkedPairs.end() )
// Only continue if we are reporting all track errors it->second.has_error = true;
if( !testSingleLayerItemAgainstItem( track, trackShape.get(), layer,
other ) )
{
if( it != checkedPairs.end() )
it->second.has_error = true;
return m_drcEngine->GetReportAllTrackErrors() && !m_drcEngine->IsCancelled(); return m_drcEngine->GetReportAllTrackErrors() && !m_drcEngine->IsCancelled();
} }
return !m_drcEngine->IsCancelled(); return !m_drcEngine->IsCancelled();
}, },
m_board->m_DRCMaxClearance ); m_board->m_DRCMaxClearance );
for( ZONE* zone : m_board->m_DRCCopperZones ) for( ZONE* zone : m_board->m_DRCCopperZones )
{ {
testItemAgainstZone( track, zone, layer ); testItemAgainstZone( track, zone, layer );
if( m_drcEngine->IsCancelled() ) if( m_drcEngine->IsCancelled() )
break; break;
}
} }
++tracks_checked;
} }
};
thread_pool& tp = GetKiCadThreadPool();
tp.push_loop( m_board->Tracks().size(), testTrack );
while( tracks_checked < static_cast<int>( m_board->Tracks().size() ) )
{
if( !reportProgress( tracks_checked, m_board->Tracks().size(), progressDelta ) )
{
tp.wait_for_tasks();
break;
}
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
} }
} }