Fix footprint double-caching memory waste

Footprint enumeration was caching parsed footprints in both the
plugin's FP_CACHE and in PreloadedFootprints, wasting about 1.2GB of
RAM. Added ClearCachedFootprints() to the plugin interface so the
adapter can delete the plugin cache after copying footprints to
PreloadedFootprints. LoadFootprint now checks PreloadedFootprints
first and clones from there, avoiding re-parsing and re-populating
the plugin cache.
This commit is contained in:
Seth Hillbrand
2026-01-23 18:58:54 -08:00
parent 14be8b204f
commit d1d850e7f9
4 changed files with 88 additions and 15 deletions
+57 -15
View File
@@ -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<std::unique_ptr<FOOTPRINT>> 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<FOOTPRINT*>( 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<FOOTPRINT*>( fp->Clone() );
else
copy = static_cast<FOOTPRINT*>( 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<const LIB_DATA*> 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() );
@@ -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
{
@@ -364,6 +364,8 @@ public:
const std::map<std::string,
UTF8>* aProperties = nullptr ) override;
bool CachesEnumeratedFootprints() const override { return true; }
bool FootprintExists( const wxString& aLibraryPath, const wxString& aFootprintName,
const std::map<std::string, UTF8>* aProperties = nullptr ) override;
@@ -380,6 +382,8 @@ public:
void FootprintDelete( const wxString& aLibraryPath, const wxString& aFootprintName,
const std::map<std::string, UTF8>* aProperties = nullptr ) override;
void ClearCachedFootprints( const wxString& aLibraryPath ) override;
long long GetLibraryTimestamp( const wxString& aLibraryPath ) const override;
void CreateLibrary( const wxString& aLibraryPath,
+18
View File
@@ -241,6 +241,14 @@ public:
virtual const FOOTPRINT* GetEnumeratedFootprint( const wxString& aLibraryPath, const wxString& aFootprintName,
const std::map<std::string, UTF8>* 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<std::string, UTF8>* 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.