Add THROTTLE class and DrainPendingEvents
Five call sites independently reimplemented the same time-gated throttle pattern. This consolidates them all into a single THROTTLE class backed by steady_clock. Three call sites duplicated the wxWidgets YieldFor workaround for timer-event backlog (wxWidgets issue #26192). This replaces them with a shared DrainPendingEvents free function.
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
#include <wx/version.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <widgets/app_progress_dialog.h>
|
||||
#include <widgets/wx_event_utils.h>
|
||||
|
||||
APP_PROGRESS_DIALOG::APP_PROGRESS_DIALOG( const wxString& aTitle, const wxString& aMessage,
|
||||
int aMaximum, wxWindow* aParent,
|
||||
@@ -54,5 +55,9 @@ bool APP_PROGRESS_DIALOG::Update( int aValue, const wxString& aNewMsg, bool* aSk
|
||||
m_appProgressIndicator.SetValue( aValue );
|
||||
}
|
||||
|
||||
return APP_PROGRESS_DIALOG_BASE::Update( aValue, aNewMsg, aSkip );
|
||||
bool diag = APP_PROGRESS_DIALOG_BASE::Update( aValue, aNewMsg, aSkip );
|
||||
|
||||
DrainPendingEvents();
|
||||
|
||||
return diag;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <wx/evtloop.h>
|
||||
#include <thread>
|
||||
#include <widgets/wx_event_utils.h>
|
||||
#include <widgets/wx_progress_reporters.h>
|
||||
|
||||
|
||||
@@ -96,10 +96,7 @@ bool WX_PROGRESS_REPORTER::updateUI()
|
||||
// Returns false when cancelled (if it's a cancellable dialog)
|
||||
bool diag = WX_PROGRESS_REPORTER_BASE::Update( cur, message );
|
||||
|
||||
// Prevent wx from queuing timer events and slowing down
|
||||
// See https://github.com/wxWidgets/wxWidgets/issues/26192
|
||||
if( wxEventLoopBase* loop = wxEventLoopBase::GetActive() )
|
||||
loop->YieldFor( wxEVT_CATEGORY_TIMER );
|
||||
DrainPendingEvents();
|
||||
|
||||
return diag;
|
||||
}
|
||||
@@ -122,8 +119,7 @@ bool GAUGE_PROGRESS_REPORTER::updateUI()
|
||||
|
||||
wxGauge::SetValue( cur );
|
||||
|
||||
if( wxEventLoopBase* loop = wxEventLoopBase::GetActive() )
|
||||
loop->YieldFor( wxEVT_CATEGORY_UI );
|
||||
DrainPendingEvents( wxEVT_CATEGORY_UI );
|
||||
|
||||
return true; // No cancel button on a wxGauge
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <base_screen.h>
|
||||
#include <confirm.h>
|
||||
#include <core/kicad_algo.h>
|
||||
#include <core/throttle.h>
|
||||
#include <eeschema_id.h>
|
||||
#include <eeschema_settings.h>
|
||||
#include <env_paths.h>
|
||||
@@ -1348,18 +1349,28 @@ void SYMBOL_EDIT_FRAME::SyncLibraries( bool aShowProgress, bool aPreloadCancelle
|
||||
APP_PROGRESS_DIALOG progressDlg( _( "Loading Symbol Libraries" ), wxEmptyString,
|
||||
m_libMgr->GetAdapter()->GetLibrariesCount(), this );
|
||||
|
||||
THROTTLE progressThrottle( std::chrono::milliseconds( 350 ) );
|
||||
int pendingProgress = 0;
|
||||
bool callbackFired = false;
|
||||
wxString pendingLibName;
|
||||
|
||||
m_libMgr->Sync( aForceRefresh,
|
||||
[&]( int progress, int max, const wxString& libName )
|
||||
{
|
||||
progressDlg.Update( progress, wxString::Format( _( "Loading library '%s'..." ), libName ) );
|
||||
pendingProgress = progress;
|
||||
pendingLibName = libName;
|
||||
callbackFired = true;
|
||||
|
||||
if( progressThrottle.Ready() )
|
||||
progressDlg.Update( progress, wxString::Format( _( "Loading library '%s'..." ), libName ) );
|
||||
} );
|
||||
|
||||
if( callbackFired )
|
||||
progressDlg.Update( pendingProgress, wxString::Format( _( "Loading library '%s'..." ), pendingLibName ) );
|
||||
}
|
||||
else if( !aPreloadCancelled )
|
||||
{
|
||||
m_libMgr->Sync( aForceRefresh,
|
||||
[&]( int progress, int max, const wxString& libName )
|
||||
{
|
||||
} );
|
||||
m_libMgr->Sync( aForceRefresh, [&]( int progress, int max, const wxString& libName ) {} );
|
||||
}
|
||||
|
||||
if( m_treePane )
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
#include <trace_helpers.h>
|
||||
#include <libraries/symbol_library_adapter.h>
|
||||
|
||||
#define PROGRESS_INTERVAL_MILLIS 33 // 30 FPS refresh rate
|
||||
|
||||
|
||||
wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>
|
||||
SYMBOL_TREE_MODEL_ADAPTER::Create( SCH_BASE_FRAME* aParent, SYMBOL_LIBRARY_ADAPTER* aManager )
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <wx/settings.h>
|
||||
|
||||
#include <core/throttle.h>
|
||||
#include <pgm_base.h>
|
||||
#include <project/project_file.h>
|
||||
#include <lib_symbol_library_manager.h>
|
||||
@@ -70,12 +71,10 @@ bool SYMBOL_TREE_SYNCHRONIZING_ADAPTER::IsContainer( const wxDataViewItem& aItem
|
||||
}
|
||||
|
||||
|
||||
#define PROGRESS_INTERVAL_MILLIS 120
|
||||
|
||||
void SYMBOL_TREE_SYNCHRONIZING_ADAPTER::Sync( const wxString& aForceRefresh,
|
||||
std::function<void( int, int, const wxString& )> aProgressCallback )
|
||||
{
|
||||
wxLongLong nextUpdate = wxGetUTCTimeMillis() + (PROGRESS_INTERVAL_MILLIS / 2);
|
||||
THROTTLE progressThrottle( std::chrono::milliseconds( 120 ) );
|
||||
|
||||
m_lastSyncHash = m_libMgr->GetHash();
|
||||
int i = 0, max = GetLibrariesCount();
|
||||
@@ -87,11 +86,8 @@ void SYMBOL_TREE_SYNCHRONIZING_ADAPTER::Sync( const wxString& aForceRefresh,
|
||||
{
|
||||
const wxString& name = it->get()->m_Name;
|
||||
|
||||
if( wxGetUTCTimeMillis() > nextUpdate )
|
||||
{
|
||||
if( progressThrottle.Ready() )
|
||||
aProgressCallback( i, max, name );
|
||||
nextUpdate = wxGetUTCTimeMillis() + PROGRESS_INTERVAL_MILLIS;
|
||||
}
|
||||
|
||||
// Check the table row directly rather than adapter->HasLibrary(), which requires the
|
||||
// library to be fully loaded. After table reloads (e.g. adding a new library), all
|
||||
@@ -137,11 +133,8 @@ void SYMBOL_TREE_SYNCHRONIZING_ADAPTER::Sync( const wxString& aForceRefresh,
|
||||
|
||||
if( m_libHashes.count( libName ) == 0 )
|
||||
{
|
||||
if( wxGetUTCTimeMillis() > nextUpdate )
|
||||
{
|
||||
if( progressThrottle.Ready() )
|
||||
aProgressCallback( i++, max, libName );
|
||||
nextUpdate = wxGetUTCTimeMillis() + PROGRESS_INTERVAL_MILLIS;
|
||||
}
|
||||
|
||||
auto optRow = adapter->GetRow( libName );
|
||||
wxCHECK2( optRow.has_value(), continue );
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
/**
|
||||
* Rate-limiter that fires at most once per interval.
|
||||
*
|
||||
* The first call to Ready() always returns true so callers can emit an
|
||||
* initial update immediately.
|
||||
*/
|
||||
class THROTTLE
|
||||
{
|
||||
public:
|
||||
explicit THROTTLE( std::chrono::milliseconds aInterval ) :
|
||||
m_interval( aInterval ),
|
||||
m_last( std::chrono::steady_clock::time_point::min() )
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true when at least \a m_interval has elapsed since the previous
|
||||
* true return (or since construction). Resets the internal clock
|
||||
* on a true return.
|
||||
*/
|
||||
bool Ready()
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if( now - m_last >= m_interval )
|
||||
{
|
||||
m_last = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::milliseconds m_interval;
|
||||
std::chrono::steady_clock::time_point m_last;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <wx/evtloop.h>
|
||||
|
||||
/**
|
||||
* Drain pending events of the given categories from the active event loop.
|
||||
*
|
||||
* During long-running operations that call progress-dialog Update() frequently,
|
||||
* wxWidgets can accumulate a backlog of timer events that makes the UI sluggish
|
||||
* after the operation completes. Calling this after each Update() prevents the
|
||||
* backlog from building up.
|
||||
*
|
||||
* See https://github.com/wxWidgets/wxWidgets/issues/26192
|
||||
*
|
||||
* @param aCategories Event categories to yield for (default wxEVT_CATEGORY_TIMER).
|
||||
*/
|
||||
inline void DrainPendingEvents( long aCategories = wxEVT_CATEGORY_TIMER )
|
||||
{
|
||||
if( wxEventLoopBase* loop = wxEventLoopBase::GetActive() )
|
||||
loop->YieldFor( aCategories );
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
#include <confirm.h>
|
||||
#include <core/throttle.h>
|
||||
#include <dialog_drc.h>
|
||||
#include <board_design_settings.h>
|
||||
#include <kiface_base.h>
|
||||
@@ -81,8 +82,8 @@ DIALOG_DRC::DIALOG_DRC( PCB_EDIT_FRAME* aEditorFrame, wxWindow* aParent ) :
|
||||
m_markersTreeModel( nullptr ),
|
||||
m_unconnectedTreeModel( nullptr ),
|
||||
m_fpWarningsTreeModel( nullptr ),
|
||||
m_lastUpdateUi( std::chrono::steady_clock::now() ),
|
||||
m_lastYieldUi( std::chrono::steady_clock::now() )
|
||||
m_updateThrottle( std::chrono::milliseconds( 100 ) ),
|
||||
m_yieldThrottle( std::chrono::milliseconds( 2000 ) )
|
||||
{
|
||||
SetName( DIALOG_DRC_WINDOW_NAME ); // Set a window name to be able to find it
|
||||
KIPLATFORM::UI::SetFloatLevel( this );
|
||||
@@ -228,27 +229,17 @@ bool DIALOG_DRC::updateUI()
|
||||
m_gauge->SetValue( newValue );
|
||||
}
|
||||
|
||||
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
|
||||
// Repaint the dialog at ~10Hz using Update() which processes only pending expose/
|
||||
// draw events without entering the full platform event loop.
|
||||
if( std::chrono::duration_cast<std::chrono::milliseconds>( now - m_lastUpdateUi ).count()
|
||||
> 100 )
|
||||
{
|
||||
if( m_updateThrottle.Ready() )
|
||||
Update();
|
||||
m_lastUpdateUi = now;
|
||||
}
|
||||
|
||||
// Yield to the event loop infrequently so the cancel button remains functional.
|
||||
// On some Linux systems with glycin-enabled gdk-pixbuf (2.44+), entering the GTK event
|
||||
// loop triggers heavyweight sandbox process spawning that can add hundreds of milliseconds
|
||||
// per call, so we keep this interval long.
|
||||
if( std::chrono::duration_cast<std::chrono::milliseconds>( now - m_lastYieldUi ).count()
|
||||
> 2000 )
|
||||
{
|
||||
if( m_yieldThrottle.Ready() )
|
||||
Pgm().App().SafeYieldFor( this, wxEVT_CATEGORY_NATIVE_EVENTS );
|
||||
m_lastYieldUi = now;
|
||||
}
|
||||
|
||||
return !m_cancelled;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <core/throttle.h>
|
||||
#include <wx/htmllbox.h>
|
||||
#include <rc_item.h>
|
||||
#include <pcb_marker.h>
|
||||
@@ -136,10 +136,7 @@ private:
|
||||
RC_TREE_MODEL* m_unconnectedTreeModel; // wx reference-counted ptr
|
||||
RC_TREE_MODEL* m_fpWarningsTreeModel; // wx reference-counted ptr
|
||||
|
||||
/// Used to throttle visual refresh in updateUi()
|
||||
std::chrono::steady_clock::time_point m_lastUpdateUi;
|
||||
|
||||
/// Used to throttle event loop yields in updateUi()
|
||||
std::chrono::steady_clock::time_point m_lastYieldUi;
|
||||
THROTTLE m_updateThrottle;
|
||||
THROTTLE m_yieldThrottle;
|
||||
};
|
||||
|
||||
|
||||
@@ -71,8 +71,6 @@ bool FP_TREE_SYNCHRONIZING_ADAPTER::IsContainer( const wxDataViewItem& aItem ) c
|
||||
}
|
||||
|
||||
|
||||
#define PROGRESS_INTERVAL_MILLIS 33 // 30 FPS refresh rate
|
||||
|
||||
void FP_TREE_SYNCHRONIZING_ADAPTER::Sync( FOOTPRINT_LIBRARY_ADAPTER* aLibs )
|
||||
{
|
||||
m_libs = aLibs;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
#include "allegro_builder.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
@@ -36,6 +35,7 @@
|
||||
#include <wx/log.h>
|
||||
|
||||
#include <core/profile.h>
|
||||
#include <core/throttle.h>
|
||||
|
||||
#include <base_units.h>
|
||||
#include <board_design_settings.h>
|
||||
@@ -4585,7 +4585,7 @@ bool BOARD_BUILDER::BuildBoard()
|
||||
const LL_WALKER fpWalker( m_brdDb.m_Header->m_LL_0x2B, m_brdDb );
|
||||
std::vector<BOARD_ITEM*> bulkAddedItems;
|
||||
|
||||
auto lastRefresh = std::chrono::steady_clock::now();
|
||||
THROTTLE refreshThrottle( std::chrono::milliseconds( 100 ) );
|
||||
|
||||
for( const BLOCK_BASE* fpContainer : fpWalker )
|
||||
{
|
||||
@@ -4626,16 +4626,8 @@ bool BOARD_BUILDER::BuildBoard()
|
||||
}
|
||||
}
|
||||
|
||||
if( m_progressReporter )
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if( now - lastRefresh >= std::chrono::milliseconds( 100 ) )
|
||||
{
|
||||
m_progressReporter->KeepRefreshing();
|
||||
lastRefresh = now;
|
||||
}
|
||||
}
|
||||
if( m_progressReporter && refreshThrottle.Ready() )
|
||||
m_progressReporter->KeepRefreshing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "convert/allegro_parser.h"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
#include <wx/sstream.h>
|
||||
@@ -33,6 +32,7 @@
|
||||
#include <wx/translation.h>
|
||||
|
||||
#include <core/profile.h>
|
||||
#include <core/throttle.h>
|
||||
#include <core/type_helpers.h>
|
||||
#include <ki_exception.h>
|
||||
|
||||
@@ -2475,7 +2475,7 @@ void ALLEGRO::PARSER::readObjects( BRD_DB& aBoard )
|
||||
|
||||
BLOCK_PARSER blockParser( m_stream, ver, aBoard.m_Header->Get_0x27_End() );
|
||||
|
||||
auto lastRefresh = std::chrono::steady_clock::now();
|
||||
THROTTLE refreshThrottle( std::chrono::milliseconds( 100 ) );
|
||||
|
||||
while( true )
|
||||
{
|
||||
@@ -2577,16 +2577,8 @@ void ALLEGRO::PARSER::readObjects( BRD_DB& aBoard )
|
||||
{
|
||||
m_progressReporter->AdvanceProgress();
|
||||
|
||||
if( ( aBoard.GetObjectCount() & 0x3F ) == 0 )
|
||||
{
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if( now - lastRefresh >= std::chrono::milliseconds( 100 ) )
|
||||
{
|
||||
m_progressReporter->KeepRefreshing();
|
||||
lastRefresh = now;
|
||||
}
|
||||
}
|
||||
if( ( aBoard.GetObjectCount() & 0x3F ) == 0 && refreshThrottle.Ready() )
|
||||
m_progressReporter->KeepRefreshing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ set( QA_COMMON_SRCS
|
||||
io/pads/test_pads_common.cpp
|
||||
io/pads/test_pads_unit_converter.cpp
|
||||
|
||||
test_throttle.cpp
|
||||
view/test_zoom_controller.cpp
|
||||
|
||||
test_netname_validator.cpp
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you may find one here:
|
||||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||||
* or you may search the http://www.gnu.org website for the version 2 license,
|
||||
* or you may write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
#include <qa_utils/wx_utils/unit_test_utils.h>
|
||||
|
||||
#include <core/throttle.h>
|
||||
#include <thread>
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE( Throttle )
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( FirstCallAlwaysReady )
|
||||
{
|
||||
THROTTLE t( std::chrono::milliseconds( 500 ) );
|
||||
BOOST_CHECK( t.Ready() );
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( SecondCallRejectsWithinInterval )
|
||||
{
|
||||
THROTTLE t( std::chrono::milliseconds( 200 ) );
|
||||
BOOST_CHECK( t.Ready() );
|
||||
BOOST_CHECK( !t.Ready() );
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE( ReadyAgainAfterInterval )
|
||||
{
|
||||
THROTTLE t( std::chrono::milliseconds( 50 ) );
|
||||
BOOST_CHECK( t.Ready() );
|
||||
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 60 ) );
|
||||
|
||||
BOOST_CHECK( t.Ready() );
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
Reference in New Issue
Block a user