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:
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user