mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-11 13:56:33 -06:00
Extend lead/trail edge support for throttled_func (#19210)
You can now create throttled functions which trigger both on the leading and trailing edge. This was then also ported to `ThrottledFunc` for `DispatcherQueue`s and used for title/taskbar updates. Closes #19188 * In CMD run: ```batch FOR /L %N IN () DO @echo %time% ``` * Doesn't hang the UI ✅ SERVICE NOTES This replays part of #19192 to make it compatible with throttled_func. (cherry picked from commit dbf740cf2c113b9613b22c0895529e68d66e14db) Service-Card-Id: PVTI_lADOAF3p4s4AxadtzgdI2TQ PVTI_lADOAF3p4s4AxadtzgdSMv0 Service-Version: 1.23
This commit is contained in:
parent
f1dad37670
commit
f18abeb62e
@ -135,12 +135,19 @@ namespace winrt::TerminalApp::implementation
|
|||||||
_isElevated = ::Microsoft::Console::Utils::IsRunningElevated();
|
_isElevated = ::Microsoft::Console::Utils::IsRunningElevated();
|
||||||
_canDragDrop = ::Microsoft::Console::Utils::CanUwpDragDrop();
|
_canDragDrop = ::Microsoft::Console::Utils::CanUwpDragDrop();
|
||||||
|
|
||||||
_reloadSettings = std::make_shared<ThrottledFuncTrailing<>>(winrt::Windows::System::DispatcherQueue::GetForCurrentThread(), std::chrono::milliseconds(100), [weakSelf = get_weak()]() {
|
_reloadSettings = std::make_shared<ThrottledFunc<>>(
|
||||||
if (auto self{ weakSelf.get() })
|
DispatcherQueue::GetForCurrentThread(),
|
||||||
{
|
til::throttled_func_options{
|
||||||
self->ReloadSettings();
|
.delay = std::chrono::milliseconds{ 100 },
|
||||||
}
|
.debounce = true,
|
||||||
});
|
.trailing = true,
|
||||||
|
},
|
||||||
|
[weakSelf = get_weak()]() {
|
||||||
|
if (auto self{ weakSelf.get() })
|
||||||
|
{
|
||||||
|
self->ReloadSettings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_languageProfileNotifier = winrt::make_self<LanguageProfileNotifier>([this]() {
|
_languageProfileNotifier = winrt::make_self<LanguageProfileNotifier>([this]() {
|
||||||
_reloadSettings->Run();
|
_reloadSettings->Run();
|
||||||
|
|||||||
@ -64,7 +64,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
bool _hasSettingsStartupActions{ false };
|
bool _hasSettingsStartupActions{ false };
|
||||||
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
|
::TerminalApp::AppCommandlineArgs _settingsAppArgs;
|
||||||
|
|
||||||
std::shared_ptr<ThrottledFuncTrailing<>> _reloadSettings;
|
std::shared_ptr<ThrottledFunc<>> _reloadSettings;
|
||||||
|
|
||||||
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings{};
|
std::vector<Microsoft::Terminal::Settings::Model::SettingsLoadWarnings> _warnings{};
|
||||||
|
|
||||||
|
|||||||
@ -35,17 +35,23 @@ namespace winrt::TerminalApp::implementation
|
|||||||
// (which should be the default, see:
|
// (which should be the default, see:
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-trackmouseevent#remarks)
|
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-trackmouseevent#remarks)
|
||||||
unsigned int hoverTimeoutMillis{ 400 };
|
unsigned int hoverTimeoutMillis{ 400 };
|
||||||
LOG_IF_WIN32_BOOL_FALSE(SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hoverTimeoutMillis, 0));
|
if (FAILED(SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hoverTimeoutMillis, 0)))
|
||||||
const auto toolTipInterval = std::chrono::milliseconds(hoverTimeoutMillis);
|
{
|
||||||
|
hoverTimeoutMillis = 400;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a ThrottledFunc for opening the tooltip after the hover
|
// Create a ThrottledFunc for opening the tooltip after the hover
|
||||||
// timeout. If we hover another button, we should make sure to call
|
// timeout. If we hover another button, we should make sure to call
|
||||||
// Run() with the new button. Calling `_displayToolTip.Run(nullptr)`,
|
// Run() with the new button. Calling `_displayToolTip.Run(nullptr)`,
|
||||||
// which will cause us to not display a tooltip, which is used when we
|
// which will cause us to not display a tooltip, which is used when we
|
||||||
// leave the control entirely.
|
// leave the control entirely.
|
||||||
_displayToolTip = std::make_shared<ThrottledFuncTrailing<Controls::Button>>(
|
_displayToolTip = std::make_shared<ThrottledFunc<Controls::Button>>(
|
||||||
dispatcher,
|
dispatcher,
|
||||||
toolTipInterval,
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ hoverTimeoutMillis },
|
||||||
|
.debounce = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[weakThis = get_weak()](Controls::Button button) {
|
[weakThis = get_weak()](Controls::Button button) {
|
||||||
// If we provide a button, then open the tooltip on that button.
|
// If we provide a button, then open the tooltip on that button.
|
||||||
// We can "dismiss" this throttled func by calling it with null,
|
// We can "dismiss" this throttled func by calling it with null,
|
||||||
|
|||||||
@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> MaximizeClick;
|
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> MaximizeClick;
|
||||||
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> CloseClick;
|
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> CloseClick;
|
||||||
|
|
||||||
std::shared_ptr<ThrottledFuncTrailing<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr };
|
std::shared_ptr<ThrottledFunc<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr };
|
||||||
std::optional<CaptionButton> _lastPressedButton{ std::nullopt };
|
std::optional<CaptionButton> _lastPressedButton{ std::nullopt };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,9 +104,13 @@ namespace winrt::TerminalApp::implementation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFuncTrailing<>>(
|
_adjustProcessPriorityThrottled = std::make_shared<ThrottledFunc<>>(
|
||||||
DispatcherQueue::GetForCurrentThread(),
|
DispatcherQueue::GetForCurrentThread(),
|
||||||
std::chrono::milliseconds{ 100 },
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 100 },
|
||||||
|
.debounce = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[=]() {
|
[=]() {
|
||||||
_adjustProcessPriority();
|
_adjustProcessPriority();
|
||||||
});
|
});
|
||||||
@ -5007,8 +5011,6 @@ namespace winrt::TerminalApp::implementation
|
|||||||
safe_void_coroutine TerminalPage::_ControlCompletionsChangedHandler(const IInspectable sender,
|
safe_void_coroutine TerminalPage::_ControlCompletionsChangedHandler(const IInspectable sender,
|
||||||
const CompletionsChangedEventArgs args)
|
const CompletionsChangedEventArgs args)
|
||||||
{
|
{
|
||||||
// This will come in on a background (not-UI, not output) thread.
|
|
||||||
|
|
||||||
// This won't even get hit if the velocity flag is disabled - we gate
|
// This won't even get hit if the velocity flag is disabled - we gate
|
||||||
// registering for the event based off of
|
// registering for the event based off of
|
||||||
// Feature_ShellCompletions::IsEnabled back in _RegisterTerminalEvents
|
// Feature_ShellCompletions::IsEnabled back in _RegisterTerminalEvents
|
||||||
|
|||||||
@ -361,7 +361,7 @@ namespace winrt::TerminalApp::implementation
|
|||||||
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
|
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);
|
||||||
bool _MoveTab(winrt::com_ptr<TerminalTab> tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
|
bool _MoveTab(winrt::com_ptr<TerminalTab> tab, const Microsoft::Terminal::Settings::Model::MoveTabArgs args);
|
||||||
|
|
||||||
std::shared_ptr<ThrottledFuncTrailing<>> _adjustProcessPriorityThrottled;
|
std::shared_ptr<ThrottledFunc<>> _adjustProcessPriorityThrottled;
|
||||||
void _adjustProcessPriority() const;
|
void _adjustProcessPriority() const;
|
||||||
|
|
||||||
template<typename F>
|
template<typename F>
|
||||||
|
|||||||
@ -946,27 +946,49 @@ namespace winrt::TerminalApp::implementation
|
|||||||
void TerminalTab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content)
|
void TerminalTab::_AttachEventHandlersToContent(const uint32_t paneId, const TerminalApp::IPaneContent& content)
|
||||||
{
|
{
|
||||||
auto weakThis{ get_weak() };
|
auto weakThis{ get_weak() };
|
||||||
auto dispatcher = TabViewItem().Dispatcher();
|
auto dispatcher = DispatcherQueue::GetForCurrentThread();
|
||||||
ContentEventTokens events{};
|
ContentEventTokens events{};
|
||||||
|
|
||||||
|
auto throttledTitleChanged = std::make_shared<ThrottledFunc<>>(
|
||||||
|
dispatcher,
|
||||||
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 200 },
|
||||||
|
.leading = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
|
[weakThis]() {
|
||||||
|
if (const auto tab = weakThis.get())
|
||||||
|
{
|
||||||
|
tab->UpdateTitle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
events.TitleChanged = content.TitleChanged(
|
events.TitleChanged = content.TitleChanged(
|
||||||
winrt::auto_revoke,
|
winrt::auto_revoke,
|
||||||
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
|
[func = std::move(throttledTitleChanged)](auto&&, auto&&) {
|
||||||
// The lambda lives in the `std::function`-style container owned by `control`. That is, when the
|
func->Run();
|
||||||
// `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to
|
});
|
||||||
// copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines.
|
|
||||||
// See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870
|
auto throttledTaskbarProgressChanged = std::make_shared<ThrottledFunc<>>(
|
||||||
const auto weakThisCopy = weakThis;
|
dispatcher,
|
||||||
co_await wil::resume_foreground(dispatcher);
|
til::throttled_func_options{
|
||||||
// Check if Tab's lifetime has expired
|
.delay = std::chrono::milliseconds{ 200 },
|
||||||
if (auto tab{ weakThisCopy.get() })
|
.leading = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
|
[weakThis]() {
|
||||||
|
if (const auto tab = weakThis.get())
|
||||||
{
|
{
|
||||||
// The title of the control changed, but not necessarily the title of the tab.
|
tab->_UpdateProgressState();
|
||||||
// Set the tab's text to the active panes' text.
|
|
||||||
tab->UpdateTitle();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
events.TaskbarProgressChanged = content.TaskbarProgressChanged(
|
||||||
|
winrt::auto_revoke,
|
||||||
|
[func = std::move(throttledTaskbarProgressChanged)](auto&&, auto&&) {
|
||||||
|
func->Run();
|
||||||
|
});
|
||||||
|
|
||||||
events.TabColorChanged = content.TabColorChanged(
|
events.TabColorChanged = content.TabColorChanged(
|
||||||
winrt::auto_revoke,
|
winrt::auto_revoke,
|
||||||
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
|
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
|
||||||
@ -982,18 +1004,6 @@ namespace winrt::TerminalApp::implementation
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
events.TaskbarProgressChanged = content.TaskbarProgressChanged(
|
|
||||||
winrt::auto_revoke,
|
|
||||||
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
|
|
||||||
const auto weakThisCopy = weakThis;
|
|
||||||
co_await wil::resume_foreground(dispatcher);
|
|
||||||
// Check if Tab's lifetime has expired
|
|
||||||
if (auto tab{ weakThisCopy.get() })
|
|
||||||
{
|
|
||||||
tab->_UpdateProgressState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
events.ConnectionStateChanged = content.ConnectionStateChanged(
|
events.ConnectionStateChanged = content.ConnectionStateChanged(
|
||||||
winrt::auto_revoke,
|
winrt::auto_revoke,
|
||||||
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
|
[dispatcher, weakThis](auto&&, auto&&) -> safe_void_coroutine {
|
||||||
|
|||||||
@ -174,8 +174,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
//
|
//
|
||||||
// NOTE: Calling UpdatePatternLocations from a background
|
// NOTE: Calling UpdatePatternLocations from a background
|
||||||
// thread is a workaround for us to hit GH#12607 less often.
|
// thread is a workaround for us to hit GH#12607 less often.
|
||||||
shared->outputIdle = std::make_unique<til::debounced_func_trailing<>>(
|
shared->outputIdle = std::make_unique<til::throttled_func<>>(
|
||||||
std::chrono::milliseconds{ 100 },
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 100 },
|
||||||
|
.debounce = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[this, weakThis = get_weak(), dispatcher = _dispatcher]() {
|
[this, weakThis = get_weak(), dispatcher = _dispatcher]() {
|
||||||
dispatcher.TryEnqueue(DispatcherQueuePriority::Normal, [weakThis]() {
|
dispatcher.TryEnqueue(DispatcherQueuePriority::Normal, [weakThis]() {
|
||||||
if (const auto self = weakThis.get(); self && !self->_IsClosing())
|
if (const auto self = weakThis.get(); self && !self->_IsClosing())
|
||||||
@ -195,8 +199,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
|
|
||||||
// If you rapidly show/hide Windows Terminal, something about GotFocus()/LostFocus() gets broken.
|
// If you rapidly show/hide Windows Terminal, something about GotFocus()/LostFocus() gets broken.
|
||||||
// We'll then receive easily 10+ such calls from WinUI the next time the application is shown.
|
// We'll then receive easily 10+ such calls from WinUI the next time the application is shown.
|
||||||
shared->focusChanged = std::make_unique<til::debounced_func_trailing<bool>>(
|
shared->focusChanged = std::make_unique<til::throttled_func<bool>>(
|
||||||
std::chrono::milliseconds{ 25 },
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 25 },
|
||||||
|
.debounce = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[this](const bool focused) {
|
[this](const bool focused) {
|
||||||
// Theoretically `debounced_func_trailing` should call `WaitForThreadpoolTimerCallbacks()`
|
// Theoretically `debounced_func_trailing` should call `WaitForThreadpoolTimerCallbacks()`
|
||||||
// with cancel=true on destruction, which should ensure that our use of `this` here is safe.
|
// with cancel=true on destruction, which should ensure that our use of `this` here is safe.
|
||||||
@ -204,9 +212,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Scrollbar updates are also expensive (XAML), so we'll throttle them as well.
|
// Scrollbar updates are also expensive (XAML), so we'll throttle them as well.
|
||||||
shared->updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
|
shared->updateScrollBar = std::make_shared<ThrottledFunc<Control::ScrollPositionChangedArgs>>(
|
||||||
_dispatcher,
|
_dispatcher,
|
||||||
std::chrono::milliseconds{ 8 },
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 8 },
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[weakThis = get_weak()](const auto& update) {
|
[weakThis = get_weak()](const auto& update) {
|
||||||
if (auto core{ weakThis.get() }; core && !core->_IsClosing())
|
if (auto core{ weakThis.get() }; core && !core->_IsClosing())
|
||||||
{
|
{
|
||||||
@ -2752,15 +2763,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
safe_void_coroutine ControlCore::_terminalCompletionsChanged(std::wstring_view menuJson,
|
void ControlCore::_terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength)
|
||||||
unsigned int replaceLength)
|
|
||||||
{
|
{
|
||||||
auto args = winrt::make_self<CompletionsChangedEventArgs>(winrt::hstring{ menuJson },
|
CompletionsChanged.raise(*this, winrt::make<CompletionsChangedEventArgs>(winrt::hstring{ menuJson }, replaceLength));
|
||||||
replaceLength);
|
|
||||||
|
|
||||||
co_await winrt::resume_background();
|
|
||||||
|
|
||||||
CompletionsChanged.raise(*this, *args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the region of text between [s.start, s.end), in buffer space
|
// Select the region of text between [s.start, s.end), in buffer space
|
||||||
|
|||||||
@ -306,9 +306,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
private:
|
private:
|
||||||
struct SharedState
|
struct SharedState
|
||||||
{
|
{
|
||||||
std::unique_ptr<til::debounced_func_trailing<>> outputIdle;
|
std::unique_ptr<til::throttled_func<>> outputIdle;
|
||||||
std::unique_ptr<til::debounced_func_trailing<bool>> focusChanged;
|
std::unique_ptr<til::throttled_func<bool>> focusChanged;
|
||||||
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> updateScrollBar;
|
std::shared_ptr<ThrottledFunc<Control::ScrollPositionChangedArgs>> updateScrollBar;
|
||||||
};
|
};
|
||||||
|
|
||||||
void _setupDispatcherAndCallbacks();
|
void _setupDispatcherAndCallbacks();
|
||||||
@ -338,7 +338,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow);
|
void _terminalSearchMissingCommand(std::wstring_view missingCommand, const til::CoordType& bufferRow);
|
||||||
void _terminalWindowSizeChanged(int32_t width, int32_t height);
|
void _terminalWindowSizeChanged(int32_t width, int32_t height);
|
||||||
|
|
||||||
safe_void_coroutine _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength);
|
void _terminalCompletionsChanged(std::wstring_view menuJson, unsigned int replaceLength);
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region RendererCallbacks
|
#pragma region RendererCallbacks
|
||||||
|
|||||||
@ -342,9 +342,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
// These three throttled functions are triggered by terminal output and interact with the UI.
|
// These three throttled functions are triggered by terminal output and interact with the UI.
|
||||||
// Since Close() is the point after which we are removed from the UI, but before the
|
// Since Close() is the point after which we are removed from the UI, but before the
|
||||||
// destructor has run, we MUST check control->_IsClosing() before actually doing anything.
|
// destructor has run, we MUST check control->_IsClosing() before actually doing anything.
|
||||||
_playWarningBell = std::make_shared<ThrottledFuncLeading>(
|
_playWarningBell = std::make_shared<ThrottledFunc<>>(
|
||||||
dispatcher,
|
dispatcher,
|
||||||
TerminalWarningBellInterval,
|
til::throttled_func_options{
|
||||||
|
.delay = TerminalWarningBellInterval,
|
||||||
|
.leading = true,
|
||||||
|
},
|
||||||
[weakThis = get_weak()]() {
|
[weakThis = get_weak()]() {
|
||||||
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
||||||
{
|
{
|
||||||
@ -352,9 +355,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
|
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
|
||||||
dispatcher,
|
dispatcher,
|
||||||
ScrollBarUpdateInterval,
|
til::throttled_func_options{
|
||||||
|
.delay = ScrollBarUpdateInterval,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[weakThis = get_weak()](const auto& update) {
|
[weakThis = get_weak()](const auto& update) {
|
||||||
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
||||||
{
|
{
|
||||||
|
|||||||
@ -284,7 +284,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
bool _quickFixesAvailable{ false };
|
bool _quickFixesAvailable{ false };
|
||||||
til::CoordType _quickFixBufferPos{};
|
til::CoordType _quickFixBufferPos{};
|
||||||
|
|
||||||
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
|
std::shared_ptr<ThrottledFunc<>> _playWarningBell;
|
||||||
|
|
||||||
struct ScrollBarUpdate
|
struct ScrollBarUpdate
|
||||||
{
|
{
|
||||||
@ -294,7 +294,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
double newViewportSize;
|
double newViewportSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
|
std::shared_ptr<ThrottledFunc<ScrollBarUpdate>> _updateScrollBar;
|
||||||
|
|
||||||
bool _isInternalScrollBarUpdate;
|
bool _isInternalScrollBarUpdate;
|
||||||
|
|
||||||
|
|||||||
@ -89,9 +89,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|||||||
{
|
{
|
||||||
if (!_updatePreviewControl)
|
if (!_updatePreviewControl)
|
||||||
{
|
{
|
||||||
_updatePreviewControl = std::make_shared<ThrottledFuncTrailing<>>(
|
_updatePreviewControl = std::make_shared<ThrottledFunc<>>(
|
||||||
winrt::Windows::System::DispatcherQueue::GetForCurrentThread(),
|
winrt::Windows::System::DispatcherQueue::GetForCurrentThread(),
|
||||||
std::chrono::milliseconds{ 100 },
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 100 },
|
||||||
|
.debounce = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[this]() {
|
[this]() {
|
||||||
const auto settings = _Profile.TermSettings();
|
const auto settings = _Profile.TermSettings();
|
||||||
_previewConnection->DisplayPowerlineGlyphs(_Profile.DefaultAppearance().HasPowerlineCharacters());
|
_previewConnection->DisplayPowerlineGlyphs(_Profile.DefaultAppearance().HasPowerlineCharacters());
|
||||||
|
|||||||
@ -33,7 +33,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
|||||||
|
|
||||||
winrt::com_ptr<PreviewConnection> _previewConnection{ nullptr };
|
winrt::com_ptr<PreviewConnection> _previewConnection{ nullptr };
|
||||||
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
|
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
|
||||||
std::shared_ptr<ThrottledFuncTrailing<>> _updatePreviewControl;
|
std::shared_ptr<ThrottledFunc<>> _updatePreviewControl;
|
||||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
|
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
|
||||||
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _AppearanceViewModelChangedRevoker;
|
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _AppearanceViewModelChangedRevoker;
|
||||||
Editor::IHostedInWindow _windowRoot;
|
Editor::IHostedInWindow _windowRoot;
|
||||||
|
|||||||
@ -96,7 +96,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||||||
ApplicationState::ApplicationState(const std::filesystem::path& stateRoot) noexcept :
|
ApplicationState::ApplicationState(const std::filesystem::path& stateRoot) noexcept :
|
||||||
_sharedPath{ stateRoot / stateFileName },
|
_sharedPath{ stateRoot / stateFileName },
|
||||||
_elevatedPath{ stateRoot / elevatedStateFileName },
|
_elevatedPath{ stateRoot / elevatedStateFileName },
|
||||||
_throttler{ std::chrono::seconds(1), [this]() { _write(); } }
|
_throttler{
|
||||||
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::seconds{ 1 },
|
||||||
|
.debounce = true,
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
|
[this]() { _write(); }
|
||||||
|
}
|
||||||
{
|
{
|
||||||
_read();
|
_read();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||||||
til::shared_mutex<state_t> _state;
|
til::shared_mutex<state_t> _state;
|
||||||
std::filesystem::path _sharedPath;
|
std::filesystem::path _sharedPath;
|
||||||
std::filesystem::path _elevatedPath;
|
std::filesystem::path _elevatedPath;
|
||||||
til::throttled_func_trailing<> _throttler;
|
til::throttled_func<> _throttler;
|
||||||
|
|
||||||
void _write() const noexcept;
|
void _write() const noexcept;
|
||||||
void _read() const noexcept;
|
void _read() const noexcept;
|
||||||
|
|||||||
@ -3,40 +3,44 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "til/throttled_func.h"
|
#include <til/throttled_func.h>
|
||||||
|
|
||||||
// ThrottledFunc is a copy of til::throttled_func,
|
// ThrottledFunc is a copy of til::throttled_func,
|
||||||
// specialized for the use with a WinRT Dispatcher.
|
// specialized for the use with a WinRT Dispatcher.
|
||||||
template<bool leading, typename... Args>
|
template<typename... Args>
|
||||||
class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<leading, Args...>>
|
class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
|
using filetime_duration = til::throttled_func_options::filetime_duration;
|
||||||
using function = std::function<void(Args...)>;
|
using function = std::function<void(Args...)>;
|
||||||
|
|
||||||
// Throttles invocations to the given `func` to not occur more often than `delay`.
|
// Throttles invocations to the given `func` to not occur more often than specified in options.
|
||||||
//
|
//
|
||||||
// If this is a:
|
// Options:
|
||||||
// * ThrottledFuncLeading: `func` will be invoked immediately and
|
// * delay: The minimum time between invocations
|
||||||
// further invocations prevented until `delay` time has passed.
|
// * leading: If true, `func` will be invoked immediately on first call
|
||||||
// * ThrottledFuncTrailing: On the first invocation a timer of `delay` time will
|
// * trailing: If true, `func` will be invoked after the delay
|
||||||
// be started. After the timer has expired `func` will be invoked just once.
|
// * debounce: If true, resets the timer on each call
|
||||||
//
|
//
|
||||||
// After `func` was invoked the state is reset and this cycle is repeated again.
|
// At least one of leading or trailing must be true.
|
||||||
ThrottledFunc(
|
ThrottledFunc(winrt::Windows::System::DispatcherQueue dispatcher, til::throttled_func_options opts, function func) :
|
||||||
winrt::Windows::System::DispatcherQueue dispatcher,
|
|
||||||
filetime_duration delay,
|
|
||||||
function func) :
|
|
||||||
_dispatcher{ std::move(dispatcher) },
|
_dispatcher{ std::move(dispatcher) },
|
||||||
_func{ std::move(func) },
|
_func{ std::move(func) },
|
||||||
_timer{ _create_timer() }
|
_timer{ _create_timer() },
|
||||||
|
_debounce{ opts.debounce },
|
||||||
|
_leading{ opts.leading },
|
||||||
|
_trailing{ opts.trailing }
|
||||||
{
|
{
|
||||||
const auto d = -delay.count();
|
if (!_leading && !_trailing)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("neither leading nor trailing");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto d = -opts.delay.count();
|
||||||
if (d >= 0)
|
if (d >= 0)
|
||||||
{
|
{
|
||||||
throw std::invalid_argument("non-positive delay specified");
|
throw std::invalid_argument("non-positive delay specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&_delay, &d, sizeof(d));
|
memcpy(&_delay, &d, sizeof(d));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,88 +58,119 @@ public:
|
|||||||
template<typename... MakeArgs>
|
template<typename... MakeArgs>
|
||||||
void Run(MakeArgs&&... args)
|
void Run(MakeArgs&&... args)
|
||||||
{
|
{
|
||||||
if (!_storage.emplace(std::forward<MakeArgs>(args)...))
|
_lead(std::make_tuple(std::forward<MakeArgs>(args)...));
|
||||||
{
|
|
||||||
_leading_edge();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifies the pending arguments for the next function
|
|
||||||
// invocation, if there is one pending currently.
|
|
||||||
//
|
|
||||||
// `func` will be invoked as func(Args...). Make sure to bind any
|
|
||||||
// arguments in `func` by reference if you'd like to modify them.
|
|
||||||
template<typename F>
|
template<typename F>
|
||||||
void ModifyPending(F func)
|
void ModifyPending(F func)
|
||||||
{
|
{
|
||||||
_storage.modify_pending(func);
|
const auto guard = _lock.lock_exclusive();
|
||||||
|
if (_pendingRunArgs)
|
||||||
|
{
|
||||||
|
std::apply(func, *_pendingRunArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void __stdcall _timer_callback(PTP_CALLBACK_INSTANCE /*instance*/, PVOID context, PTP_TIMER /*timer*/) noexcept
|
static void __stdcall _timer_callback(PTP_CALLBACK_INSTANCE /*instance*/, PVOID context, PTP_TIMER /*timer*/) noexcept
|
||||||
|
try
|
||||||
{
|
{
|
||||||
static_cast<ThrottledFunc*>(context)->_trailing_edge();
|
static_cast<ThrottledFunc*>(context)->_trail();
|
||||||
}
|
}
|
||||||
|
CATCH_LOG()
|
||||||
|
|
||||||
void _leading_edge()
|
wil::unique_threadpool_timer _create_timer()
|
||||||
{
|
|
||||||
if constexpr (leading)
|
|
||||||
{
|
|
||||||
_dispatcher.TryEnqueue(winrt::Windows::System::DispatcherQueuePriority::Normal, [weakSelf = this->weak_from_this()]() {
|
|
||||||
if (auto self{ weakSelf.lock() })
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
self->_func();
|
|
||||||
}
|
|
||||||
CATCH_LOG();
|
|
||||||
|
|
||||||
SetThreadpoolTimerEx(self->_timer.get(), &self->_delay, 0, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SetThreadpoolTimerEx(_timer.get(), &_delay, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _trailing_edge()
|
|
||||||
{
|
|
||||||
if constexpr (leading)
|
|
||||||
{
|
|
||||||
_storage.reset();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_dispatcher.TryEnqueue(winrt::Windows::System::DispatcherQueuePriority::Normal, [weakSelf = this->weak_from_this()]() {
|
|
||||||
if (auto self{ weakSelf.lock() })
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
self->_storage.apply(self->_func);
|
|
||||||
}
|
|
||||||
CATCH_LOG();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline wil::unique_threadpool_timer _create_timer()
|
|
||||||
{
|
{
|
||||||
wil::unique_threadpool_timer timer{ CreateThreadpoolTimer(&_timer_callback, this, nullptr) };
|
wil::unique_threadpool_timer timer{ CreateThreadpoolTimer(&_timer_callback, this, nullptr) };
|
||||||
THROW_LAST_ERROR_IF(!timer);
|
THROW_LAST_ERROR_IF(!timer);
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILETIME _delay;
|
void _lead(std::tuple<Args...> args)
|
||||||
|
{
|
||||||
|
bool timerRunning = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto guard = _lock.lock_exclusive();
|
||||||
|
|
||||||
|
timerRunning = _timerRunning;
|
||||||
|
_timerRunning = true;
|
||||||
|
|
||||||
|
if (!timerRunning && _leading)
|
||||||
|
{
|
||||||
|
// Call the function immediately on the leading edge.
|
||||||
|
// See below (out of lock).
|
||||||
|
}
|
||||||
|
else if (_trailing)
|
||||||
|
{
|
||||||
|
_pendingRunArgs.emplace(std::move(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto execute = !timerRunning && _leading;
|
||||||
|
const auto schedule = !timerRunning || _debounce;
|
||||||
|
|
||||||
|
if (execute)
|
||||||
|
{
|
||||||
|
_execute(std::move(args), schedule);
|
||||||
|
}
|
||||||
|
else if (schedule)
|
||||||
|
{
|
||||||
|
SetThreadpoolTimerEx(_timer.get(), &_delay, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _trail()
|
||||||
|
{
|
||||||
|
decltype(_pendingRunArgs) args;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto guard = _lock.lock_exclusive();
|
||||||
|
|
||||||
|
_timerRunning = false;
|
||||||
|
args = std::exchange(_pendingRunArgs, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args)
|
||||||
|
{
|
||||||
|
_execute(std::move(*args), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _execute(std::tuple<Args...> args, bool schedule)
|
||||||
|
{
|
||||||
|
_dispatcher.TryEnqueue(
|
||||||
|
winrt::Windows::System::DispatcherQueuePriority::Normal,
|
||||||
|
[weakSelf = this->weak_from_this(), args = std::move(args), schedule]() mutable {
|
||||||
|
if (auto self{ weakSelf.lock() })
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::apply(self->_func, std::move(args));
|
||||||
|
}
|
||||||
|
CATCH_LOG();
|
||||||
|
|
||||||
|
if (schedule)
|
||||||
|
{
|
||||||
|
SetThreadpoolTimerEx(self->_timer.get(), &self->_delay, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
winrt::Windows::System::DispatcherQueue _dispatcher;
|
winrt::Windows::System::DispatcherQueue _dispatcher;
|
||||||
|
|
||||||
|
// Everything below this point is just like til::throttled_func.
|
||||||
|
|
||||||
function _func;
|
function _func;
|
||||||
|
|
||||||
wil::unique_threadpool_timer _timer;
|
wil::unique_threadpool_timer _timer;
|
||||||
til::details::throttled_func_storage<Args...> _storage;
|
FILETIME _delay;
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Args>
|
wil::srwlock _lock;
|
||||||
using ThrottledFuncTrailing = ThrottledFunc<false, Args...>;
|
std::optional<std::tuple<Args...>> _pendingRunArgs;
|
||||||
using ThrottledFuncLeading = ThrottledFunc<true>;
|
bool _timerRunning = false;
|
||||||
|
|
||||||
|
bool _debounce;
|
||||||
|
bool _leading;
|
||||||
|
bool _trailing;
|
||||||
|
};
|
||||||
|
|||||||
@ -288,9 +288,12 @@ void AppHost::Initialize()
|
|||||||
// the PTY requesting a change to the window state and the Terminal
|
// the PTY requesting a change to the window state and the Terminal
|
||||||
// realizing it, but should mitigate issues where the Terminal and PTY get
|
// realizing it, but should mitigate issues where the Terminal and PTY get
|
||||||
// de-sync'd.
|
// de-sync'd.
|
||||||
_showHideWindowThrottler = std::make_shared<ThrottledFuncTrailing<bool>>(
|
_showHideWindowThrottler = std::make_shared<ThrottledFunc<bool>>(
|
||||||
winrt::Windows::System::DispatcherQueue::GetForCurrentThread(),
|
winrt::Windows::System::DispatcherQueue::GetForCurrentThread(),
|
||||||
std::chrono::milliseconds(200),
|
til::throttled_func_options{
|
||||||
|
.delay = std::chrono::milliseconds{ 200 },
|
||||||
|
.trailing = true,
|
||||||
|
},
|
||||||
[this](const bool show) {
|
[this](const bool show) {
|
||||||
_window->ShowWindowChanged(show);
|
_window->ShowWindowChanged(show);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -39,7 +39,7 @@ private:
|
|||||||
std::unique_ptr<IslandWindow> _window;
|
std::unique_ptr<IslandWindow> _window;
|
||||||
winrt::TerminalApp::AppLogic _appLogic{ nullptr };
|
winrt::TerminalApp::AppLogic _appLogic{ nullptr };
|
||||||
winrt::TerminalApp::TerminalWindow _windowLogic{ nullptr };
|
winrt::TerminalApp::TerminalWindow _windowLogic{ nullptr };
|
||||||
std::shared_ptr<ThrottledFuncTrailing<bool>> _showHideWindowThrottler;
|
std::shared_ptr<ThrottledFunc<bool>> _showHideWindowThrottler;
|
||||||
SafeDispatcherTimer _frameTimer;
|
SafeDispatcherTimer _frameTimer;
|
||||||
LARGE_INTEGER _lastActivatedTime{};
|
LARGE_INTEGER _lastActivatedTime{};
|
||||||
winrt::guid _virtualDesktopId{};
|
winrt::guid _virtualDesktopId{};
|
||||||
|
|||||||
@ -5,120 +5,49 @@
|
|||||||
|
|
||||||
namespace til
|
namespace til
|
||||||
{
|
{
|
||||||
namespace details
|
struct throttled_func_options
|
||||||
{
|
{
|
||||||
template<typename... Args>
|
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
|
||||||
class throttled_func_storage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
template<typename... MakeArgs>
|
|
||||||
bool emplace(MakeArgs&&... args)
|
|
||||||
{
|
|
||||||
std::unique_lock guard{ _lock };
|
|
||||||
const bool hadValue = _pendingRunArgs.has_value();
|
|
||||||
_pendingRunArgs.emplace(std::forward<MakeArgs>(args)...);
|
|
||||||
return hadValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename F>
|
filetime_duration delay{};
|
||||||
void modify_pending(F f)
|
bool debounce = false;
|
||||||
{
|
bool leading = false;
|
||||||
std::unique_lock guard{ _lock };
|
bool trailing = false;
|
||||||
if (_pendingRunArgs)
|
};
|
||||||
{
|
|
||||||
std::apply(f, *_pendingRunArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void apply(const auto& func)
|
template<typename... Args>
|
||||||
{
|
|
||||||
decltype(_pendingRunArgs) args;
|
|
||||||
{
|
|
||||||
std::unique_lock guard{ _lock };
|
|
||||||
args = std::exchange(_pendingRunArgs, std::nullopt);
|
|
||||||
}
|
|
||||||
// Theoretically it should always have a value, because the throttled_func
|
|
||||||
// should not call the callback without there being a reason.
|
|
||||||
// But in practice a failure here was observed at least once.
|
|
||||||
// It's unknown to me what caused it, so the best we can do is avoid a crash.
|
|
||||||
assert(args.has_value());
|
|
||||||
if (args)
|
|
||||||
{
|
|
||||||
std::apply(func, *args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit operator bool() const
|
|
||||||
{
|
|
||||||
std::shared_lock guard{ _lock };
|
|
||||||
return _pendingRunArgs.has_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// std::mutex uses imperfect Critical Sections on Windows.
|
|
||||||
// --> std::shared_mutex uses SRW locks that are small and fast.
|
|
||||||
mutable std::shared_mutex _lock;
|
|
||||||
std::optional<std::tuple<Args...>> _pendingRunArgs;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
class throttled_func_storage<>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool emplace()
|
|
||||||
{
|
|
||||||
return _isPending.exchange(true, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void apply(const auto& func)
|
|
||||||
{
|
|
||||||
if (_isPending.exchange(false, std::memory_order_relaxed))
|
|
||||||
{
|
|
||||||
func();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
_isPending.store(false, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit operator bool() const
|
|
||||||
{
|
|
||||||
return _isPending.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::atomic<bool> _isPending;
|
|
||||||
};
|
|
||||||
} // namespace details
|
|
||||||
|
|
||||||
template<bool Debounce, bool Leading, typename... Args>
|
|
||||||
class throttled_func
|
class throttled_func
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using filetime_duration = std::chrono::duration<int64_t, std::ratio<1, 10000000>>;
|
using filetime_duration = throttled_func_options::filetime_duration;
|
||||||
using function = std::function<void(Args...)>;
|
using function = std::function<void(Args...)>;
|
||||||
|
|
||||||
// Throttles invocations to the given `func` to not occur more often than `delay`.
|
// Throttles invocations to the given `func` to not occur more often than specified in options.
|
||||||
//
|
//
|
||||||
// If this is a:
|
// Options:
|
||||||
// * throttled_func_leading: `func` will be invoked immediately and
|
// * delay: The minimum time between invocations
|
||||||
// further invocations prevented until `delay` time has passed.
|
// * debounce: If true, resets the timer on each call
|
||||||
// * throttled_func_trailing: On the first invocation a timer of `delay` time will
|
// * leading: If true, `func` will be invoked immediately on first call
|
||||||
// be started. After the timer has expired `func` will be invoked just once.
|
// * trailing: If true, `func` will be invoked after the delay
|
||||||
//
|
//
|
||||||
// After `func` was invoked the state is reset and this cycle is repeated again.
|
// At least one of leading or trailing must be true.
|
||||||
throttled_func(filetime_duration delay, function func) :
|
throttled_func(throttled_func_options opts, function func) :
|
||||||
_func{ std::move(func) },
|
_func{ std::move(func) },
|
||||||
_timer{ _createTimer() }
|
_timer{ _create_timer() },
|
||||||
|
_debounce{ opts.debounce },
|
||||||
|
_leading{ opts.leading },
|
||||||
|
_trailing{ opts.trailing }
|
||||||
{
|
{
|
||||||
const auto d = -delay.count();
|
if (!_leading && !_trailing)
|
||||||
|
{
|
||||||
|
throw std::invalid_argument("neither leading nor trailing");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto d = -opts.delay.count();
|
||||||
if (d >= 0)
|
if (d >= 0)
|
||||||
{
|
{
|
||||||
throw std::invalid_argument("non-positive delay specified");
|
throw std::invalid_argument("non-positive delay specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(&_delay, &d, sizeof(d));
|
memcpy(&_delay, &d, sizeof(d));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,27 +68,7 @@ namespace til
|
|||||||
template<typename... MakeArgs>
|
template<typename... MakeArgs>
|
||||||
void operator()(MakeArgs&&... args)
|
void operator()(MakeArgs&&... args)
|
||||||
{
|
{
|
||||||
const auto hadValue = _storage.emplace(std::forward<MakeArgs>(args)...);
|
_lead(std::make_tuple(std::forward<MakeArgs>(args)...));
|
||||||
|
|
||||||
if constexpr (Debounce)
|
|
||||||
{
|
|
||||||
SetThreadpoolTimerEx(_timer.get(), &_delay, 0, 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!hadValue)
|
|
||||||
{
|
|
||||||
SetThreadpoolTimerEx(_timer.get(), &_delay, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (Leading)
|
|
||||||
{
|
|
||||||
if (!hadValue)
|
|
||||||
{
|
|
||||||
_func();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modifies the pending arguments for the next function
|
// Modifies the pending arguments for the next function
|
||||||
@ -170,7 +79,11 @@ namespace til
|
|||||||
template<typename F>
|
template<typename F>
|
||||||
void modify_pending(F func)
|
void modify_pending(F func)
|
||||||
{
|
{
|
||||||
_storage.modify_pending(func);
|
const auto guard = _lock.lock_exclusive();
|
||||||
|
if (_pendingRunArgs)
|
||||||
|
{
|
||||||
|
std::apply(func, *_pendingRunArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Makes sure that the currently pending timer is executed
|
// Makes sure that the currently pending timer is executed
|
||||||
@ -190,36 +103,76 @@ namespace til
|
|||||||
static void __stdcall _timer_callback(PTP_CALLBACK_INSTANCE /*instance*/, PVOID context, PTP_TIMER /*timer*/) noexcept
|
static void __stdcall _timer_callback(PTP_CALLBACK_INSTANCE /*instance*/, PVOID context, PTP_TIMER /*timer*/) noexcept
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const auto self = static_cast<throttled_func*>(context);
|
static_cast<throttled_func*>(context)->_trail();
|
||||||
if constexpr (Leading)
|
|
||||||
{
|
|
||||||
self->_storage.reset();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
self->_storage.apply(self->_func);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
CATCH_LOG()
|
CATCH_LOG()
|
||||||
|
|
||||||
wil::unique_threadpool_timer _createTimer()
|
wil::unique_threadpool_timer _create_timer()
|
||||||
{
|
{
|
||||||
wil::unique_threadpool_timer timer{ CreateThreadpoolTimer(&_timer_callback, this, nullptr) };
|
wil::unique_threadpool_timer timer{ CreateThreadpoolTimer(&_timer_callback, this, nullptr) };
|
||||||
THROW_LAST_ERROR_IF(!timer);
|
THROW_LAST_ERROR_IF(!timer);
|
||||||
return timer;
|
return timer;
|
||||||
}
|
}
|
||||||
|
|
||||||
FILETIME _delay;
|
void _lead(std::tuple<Args...> args)
|
||||||
|
{
|
||||||
|
bool timerRunning = false;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto guard = _lock.lock_exclusive();
|
||||||
|
|
||||||
|
timerRunning = _timerRunning;
|
||||||
|
_timerRunning = true;
|
||||||
|
|
||||||
|
if (!timerRunning && _leading)
|
||||||
|
{
|
||||||
|
// Call the function immediately on the leading edge.
|
||||||
|
// See below (out of lock).
|
||||||
|
}
|
||||||
|
else if (_trailing)
|
||||||
|
{
|
||||||
|
_pendingRunArgs.emplace(std::move(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timerRunning && _leading)
|
||||||
|
{
|
||||||
|
std::apply(_func, std::move(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!timerRunning || _debounce)
|
||||||
|
{
|
||||||
|
SetThreadpoolTimerEx(_timer.get(), &_delay, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _trail()
|
||||||
|
{
|
||||||
|
decltype(_pendingRunArgs) args;
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto guard = _lock.lock_exclusive();
|
||||||
|
|
||||||
|
_timerRunning = false;
|
||||||
|
args = std::exchange(_pendingRunArgs, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args)
|
||||||
|
{
|
||||||
|
std::apply(_func, *std::move(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _func;
|
function _func;
|
||||||
wil::unique_threadpool_timer _timer;
|
wil::unique_threadpool_timer _timer;
|
||||||
details::throttled_func_storage<Args...> _storage;
|
FILETIME _delay;
|
||||||
|
|
||||||
|
wil::srwlock _lock;
|
||||||
|
std::optional<std::tuple<Args...>> _pendingRunArgs;
|
||||||
|
bool _timerRunning = false;
|
||||||
|
|
||||||
|
bool _debounce;
|
||||||
|
bool _leading;
|
||||||
|
bool _trailing;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
using throttled_func_trailing = throttled_func<false, false, Args...>;
|
|
||||||
using throttled_func_leading = throttled_func<false, true>;
|
|
||||||
|
|
||||||
template<typename... Args>
|
|
||||||
using debounced_func_trailing = throttled_func<true, false, Args...>;
|
|
||||||
using debounced_func_leading = throttled_func<true, true>;
|
|
||||||
} // namespace til
|
} // namespace til
|
||||||
|
|||||||
@ -19,21 +19,25 @@ class ThrottledFuncTests
|
|||||||
TEST_METHOD(Basic)
|
TEST_METHOD(Basic)
|
||||||
{
|
{
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
using throttled_func = til::throttled_func_trailing<bool>;
|
using throttled_func = til::throttled_func<bool>;
|
||||||
|
|
||||||
til::latch latch{ 2 };
|
til::latch latch{ 2 };
|
||||||
|
|
||||||
std::unique_ptr<throttled_func> tf;
|
std::unique_ptr<throttled_func> tf;
|
||||||
tf = std::make_unique<throttled_func>(10ms, [&](bool reschedule) {
|
tf = std::make_unique<throttled_func>(
|
||||||
latch.count_down();
|
til::throttled_func_options{
|
||||||
|
.delay = 10ms,
|
||||||
|
.trailing = true },
|
||||||
|
[&](bool reschedule) {
|
||||||
|
latch.count_down();
|
||||||
|
|
||||||
// This will ensure that the callback is called even if we
|
// This will ensure that the callback is called even if we
|
||||||
// invoke the throttled_func from inside the callback itself.
|
// invoke the throttled_func from inside the callback itself.
|
||||||
if (reschedule)
|
if (reschedule)
|
||||||
{
|
{
|
||||||
tf->operator()(false);
|
tf->operator()(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// This will ensure that the throttled_func invokes the callback in general.
|
// This will ensure that the throttled_func invokes the callback in general.
|
||||||
tf->operator()(true);
|
tf->operator()(true);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user