diff --git a/pcbnew/footprint_library_adapter.cpp b/pcbnew/footprint_library_adapter.cpp index 116c9993bf..97b501bcd4 100644 --- a/pcbnew/footprint_library_adapter.cpp +++ b/pcbnew/footprint_library_adapter.cpp @@ -63,39 +63,53 @@ void FOOTPRINT_LIBRARY_ADAPTER::enumerateLibrary( LIB_DATA* aLib ) wxString uri = getUri( aLib->row ); wxString nickname = aLib->row->Nickname(); + // FootprintEnumerate populates the plugin's internal FP_CACHE with parsed footprints plugin->FootprintEnumerate( namesAS, uri, false, &options ); std::vector> footprints; footprints.reserve( namesAS.size() ); + // For plugins with internal caches (like kicad_sexpr), GetEnumeratedFootprint returns + // a borrowed pointer and ClearCachedFootprints handles cleanup. For other plugins, + // GetEnumeratedFootprint allocates new memory that we must delete after cloning. + const bool pluginCaches = plugin->CachesEnumeratedFootprints(); + for( const wxString& footprintName : namesAS ) { - FOOTPRINT* footprint = nullptr; - try { - footprint = plugin->FootprintLoad( uri, footprintName, false, &options ); + const FOOTPRINT* cached = plugin->GetEnumeratedFootprint( uri, footprintName, &options ); + + if( !cached ) + continue; + + FOOTPRINT* footprint = static_cast( cached->Duplicate( IGNORE_PARENT_GROUP ) ); + footprint->SetParent( nullptr ); + + // For non-caching plugins, delete the allocated footprint now that we've cloned it + if( !pluginCaches ) + delete cached; + + LIB_ID id = footprint->GetFPID(); + id.SetLibNickname( nickname ); + footprint->SetFPID( id ); + footprints.emplace_back( footprint ); } catch( IO_ERROR& e ) { - wxLogTrace( traceLibraries, "FP: Exception loading footprint %s from %s: %s", - footprintName, nickname, e.What() ); - continue; + wxLogTrace( traceLibraries, "FP: %s:%s enumeration error: %s", + nickname, footprintName, e.What() ); } - - if( !footprint ) - continue; - - LIB_ID id = footprint->GetFPID(); - id.SetLibNickname( nickname ); - footprint->SetFPID( id ); - footprints.emplace_back( footprint ); } { std::unique_lock lock( PreloadedFootprintsMutex ); PreloadedFootprints.Get()[nickname] = std::move( footprints ); } + + // Clear the plugin's FP_CACHE now that we've copied footprints to PreloadedFootprints. + // This eliminates the double-caching that was consuming ~1.2GB of extra RAM. + plugin->ClearCachedFootprints( uri ); } @@ -231,11 +245,39 @@ bool FOOTPRINT_LIBRARY_ADAPTER::FootprintExists( const wxString& aNickname, cons FOOTPRINT* FOOTPRINT_LIBRARY_ADAPTER::LoadFootprint( const wxString& aNickname, const wxString& aName, bool aKeepUUID ) { + // First check if the footprint is in PreloadedFootprints and clone from there. + // This avoids re-parsing the file and keeps FP_CACHE from being repopulated. + { + std::shared_lock lock( PreloadedFootprintsMutex ); + auto libIt = PreloadedFootprints.Get().find( aNickname ); + + if( libIt != PreloadedFootprints.Get().end() ) + { + for( const auto& fp : libIt->second ) + { + if( fp->GetFPID().GetLibItemName() == UTF8( aName ) ) + { + FOOTPRINT* copy; + + if( aKeepUUID ) + copy = static_cast( fp->Clone() ); + else + copy = static_cast( fp->Duplicate( IGNORE_PARENT_GROUP ) ); + + copy->SetParent( nullptr ); + return copy; + } + } + } + } + + // Footprint not found in PreloadedFootprints, fall back to plugin. + // This re-parses the file but is needed for footprints not yet enumerated. if( std::optional lib = fetchIfLoaded( aNickname ) ) { try { - if( FOOTPRINT* footprint = pcbplugin( *lib )->FootprintLoad( getUri( ( *lib )->row ), aName ) ) + if( FOOTPRINT* footprint = pcbplugin( *lib )->FootprintLoad( getUri( ( *lib )->row ), aName, aKeepUUID ) ) { LIB_ID id = footprint->GetFPID(); id.SetLibNickname( ( *lib )->row->Nickname() ); diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp index 01f9288872..1042efcb88 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp @@ -3404,6 +3404,15 @@ void PCB_IO_KICAD_SEXPR::FootprintDelete( const wxString& aLibraryPath, } +void PCB_IO_KICAD_SEXPR::ClearCachedFootprints( const wxString& aLibraryPath ) +{ + if( m_cache && m_cache->IsPath( aLibraryPath ) ) + { + delete m_cache; + m_cache = nullptr; + } +} + long long PCB_IO_KICAD_SEXPR::GetLibraryTimestamp( const wxString& aLibraryPath ) const { diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h index 8a955bb222..a7ebbf7424 100644 --- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h +++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.h @@ -364,6 +364,8 @@ public: const std::map* aProperties = nullptr ) override; + bool CachesEnumeratedFootprints() const override { return true; } + bool FootprintExists( const wxString& aLibraryPath, const wxString& aFootprintName, const std::map* aProperties = nullptr ) override; @@ -380,6 +382,8 @@ public: void FootprintDelete( const wxString& aLibraryPath, const wxString& aFootprintName, const std::map* aProperties = nullptr ) override; + void ClearCachedFootprints( const wxString& aLibraryPath ) override; + long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override; void CreateLibrary( const wxString& aLibraryPath, diff --git a/pcbnew/pcb_io/pcb_io.h b/pcbnew/pcb_io/pcb_io.h index c2ea53a2fa..03ebc86fe9 100644 --- a/pcbnew/pcb_io/pcb_io.h +++ b/pcbnew/pcb_io/pcb_io.h @@ -241,6 +241,14 @@ public: virtual const FOOTPRINT* GetEnumeratedFootprint( const wxString& aLibraryPath, const wxString& aFootprintName, const std::map* aProperties = nullptr ); + /** + * Return true if GetEnumeratedFootprint() returns a borrowed pointer from an internal cache. + * + * When true, the caller must NOT delete the returned pointer. When false (the default), + * GetEnumeratedFootprint() allocates a new FOOTPRINT and the caller owns the memory. + */ + virtual bool CachesEnumeratedFootprints() const { return false; } + /** * Check for the existence of a footprint. */ @@ -283,6 +291,16 @@ public: virtual void FootprintDelete( const wxString& aLibraryPath, const wxString& aFootprintName, const std::map* aProperties = nullptr ); + /** + * Clear any cached footprint data for the given library path. + * + * This is used to free memory after footprints have been loaded into another cache. + * The default implementation does nothing; plugins with caches should override. + * + * @param aLibraryPath is the path of the library whose cache should be cleared. + */ + virtual void ClearCachedFootprints( const wxString& aLibraryPath ) {} + /** * Append supported PLUGIN options to @a aListToAppenTo along with internationalized * descriptions.