Split ThrottledFunc into Leading and Trailing variants (#10133)
## Summary of the Pull Request This replaces `ThrottledFunc` with two variants: * `ThrottledFuncLeading` invokes the callback immediately and blocks further calls for the given duration * `ThrottledFuncTrailing` blocks calls for the given duration and then invokes the callback ## References * #9270 - `ThrottledFuncLeading` will allow the pane to flash immediately for a BEL, but block further BELs until the animation finished ## PR Checklist * [x] I work here * [ ] Tests added/passed ## Validation Steps Performed * [x] Ensured scrolling still works
This commit is contained in:
parent
a8e4bedae3
commit
13f0b8e007
|
@ -110,37 +110,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
// Core eventually won't have access to. When we get to
|
||||
// https://github.com/microsoft/terminal/projects/5#card-50760282
|
||||
// then we'll move the applicable ones.
|
||||
_tsfTryRedrawCanvas = std::make_shared<ThrottledFunc<>>(
|
||||
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
|
||||
Dispatcher(),
|
||||
TsfRedrawInterval,
|
||||
[weakThis = get_weak()]() {
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
control->TSFInputControl().TryRedrawCanvas();
|
||||
}
|
||||
},
|
||||
TsfRedrawInterval,
|
||||
Dispatcher());
|
||||
});
|
||||
|
||||
_updatePatternLocations = std::make_shared<ThrottledFunc<>>(
|
||||
_updatePatternLocations = std::make_shared<ThrottledFuncTrailing<>>(
|
||||
Dispatcher(),
|
||||
UpdatePatternLocationsInterval,
|
||||
[weakThis = get_weak()]() {
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
control->_core->UpdatePatternLocations();
|
||||
}
|
||||
},
|
||||
UpdatePatternLocationsInterval,
|
||||
Dispatcher());
|
||||
});
|
||||
|
||||
_playWarningBell = std::make_shared<ThrottledFunc<>>(
|
||||
_playWarningBell = std::make_shared<ThrottledFuncLeading>(
|
||||
Dispatcher(),
|
||||
TerminalWarningBellInterval,
|
||||
[weakThis = get_weak()]() {
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
control->_WarningBellHandlers(*control, nullptr);
|
||||
}
|
||||
},
|
||||
TerminalWarningBellInterval,
|
||||
Dispatcher());
|
||||
});
|
||||
|
||||
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
|
||||
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
|
||||
Dispatcher(),
|
||||
ScrollBarUpdateInterval,
|
||||
[weakThis = get_weak()](const auto& update) {
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
|
@ -159,9 +161,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
|
||||
control->_isInternalScrollBarUpdate = false;
|
||||
}
|
||||
},
|
||||
ScrollBarUpdateInterval,
|
||||
Dispatcher());
|
||||
});
|
||||
|
||||
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
|
||||
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
|
||||
|
|
|
@ -151,9 +151,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
bool _focused;
|
||||
bool _initializedTerminal;
|
||||
|
||||
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
|
||||
std::shared_ptr<ThrottledFunc<>> _updatePatternLocations;
|
||||
std::shared_ptr<ThrottledFunc<>> _playWarningBell;
|
||||
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
|
||||
std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations;
|
||||
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
|
||||
|
||||
struct ScrollBarUpdate
|
||||
{
|
||||
|
@ -162,7 +162,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||
double newMinimum;
|
||||
double newViewportSize;
|
||||
};
|
||||
std::shared_ptr<ThrottledFunc<ScrollBarUpdate>> _updateScrollBar;
|
||||
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
|
||||
bool _isInternalScrollBarUpdate;
|
||||
|
||||
// Auto scroll occurs when user, while selecting, drags cursor outside viewport. View is then scrolled to 'follow' the cursor.
|
||||
|
|
|
@ -9,6 +9,68 @@ Module Name:
|
|||
#pragma once
|
||||
#include "pch.h"
|
||||
|
||||
template<typename... Args>
|
||||
class ThrottledFuncStorage
|
||||
{
|
||||
public:
|
||||
template<typename... MakeArgs>
|
||||
bool Emplace(MakeArgs&&... args)
|
||||
{
|
||||
std::scoped_lock guard{ _lock };
|
||||
|
||||
const bool hadValue = _pendingRunArgs.has_value();
|
||||
_pendingRunArgs.emplace(std::forward<MakeArgs>(args)...);
|
||||
return hadValue;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
void ModifyPending(F f)
|
||||
{
|
||||
std::scoped_lock guard{ _lock };
|
||||
|
||||
if (_pendingRunArgs.has_value())
|
||||
{
|
||||
std::apply(f, _pendingRunArgs.value());
|
||||
}
|
||||
}
|
||||
|
||||
std::tuple<Args...> Extract()
|
||||
{
|
||||
decltype(_pendingRunArgs) args;
|
||||
std::scoped_lock guard{ _lock };
|
||||
_pendingRunArgs.swap(args);
|
||||
return args.value();
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex _lock;
|
||||
std::optional<std::tuple<Args...>> _pendingRunArgs;
|
||||
};
|
||||
|
||||
template<>
|
||||
class ThrottledFuncStorage<>
|
||||
{
|
||||
public:
|
||||
bool Emplace()
|
||||
{
|
||||
return _isRunPending.test_and_set(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::tuple<> Extract()
|
||||
{
|
||||
Reset();
|
||||
return {};
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
_isRunPending.clear(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_flag _isRunPending;
|
||||
};
|
||||
|
||||
// Class Description:
|
||||
// - Represents a function that takes arguments and whose invocation is
|
||||
// delayed by a specified duration and rate-limited such that if the code
|
||||
|
@ -16,16 +78,16 @@ Module Name:
|
|||
// pending, then the previous call with the previous arguments will be
|
||||
// cancelled and the call will be made with the new arguments instead.
|
||||
// - The function will be run on the the specified dispatcher.
|
||||
template<typename... Args>
|
||||
class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>>
|
||||
template<bool leading, typename... Args>
|
||||
class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<leading, Args...>>
|
||||
{
|
||||
public:
|
||||
using Func = std::function<void(Args...)>;
|
||||
|
||||
ThrottledFunc(Func func, winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher) :
|
||||
_func{ func },
|
||||
_delay{ delay },
|
||||
_dispatcher{ dispatcher }
|
||||
ThrottledFunc(winrt::Windows::UI::Core::CoreDispatcher dispatcher, winrt::Windows::Foundation::TimeSpan delay, Func func) :
|
||||
_dispatcher{ std::move(dispatcher) },
|
||||
_delay{ std::move(delay) },
|
||||
_func{ std::move(func) }
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -37,26 +99,16 @@ public:
|
|||
// - This method is always thread-safe. It can be called multiple times on
|
||||
// different threads.
|
||||
// Arguments:
|
||||
// - arg: the argument to pass to the function
|
||||
// - args: the arguments to pass to the function
|
||||
// Return Value:
|
||||
// - <none>
|
||||
template<typename... MakeArgs>
|
||||
void Run(MakeArgs&&... args)
|
||||
{
|
||||
if (!_storage.Emplace(std::forward<MakeArgs>(args)...))
|
||||
{
|
||||
std::lock_guard guard{ _lock };
|
||||
|
||||
bool hadValue = _pendingRunArgs.has_value();
|
||||
_pendingRunArgs.emplace(std::forward<MakeArgs>(args)...);
|
||||
|
||||
if (hadValue)
|
||||
{
|
||||
// already pending
|
||||
return;
|
||||
}
|
||||
_Fire();
|
||||
}
|
||||
|
||||
_Fire(_delay, _dispatcher, this->weak_from_this());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -81,93 +133,54 @@ public:
|
|||
template<typename F>
|
||||
void ModifyPending(F f)
|
||||
{
|
||||
std::lock_guard guard{ _lock };
|
||||
|
||||
if (_pendingRunArgs.has_value())
|
||||
{
|
||||
std::apply(f, _pendingRunArgs.value());
|
||||
}
|
||||
_storage.ModifyPending(f);
|
||||
}
|
||||
|
||||
private:
|
||||
static winrt::fire_and_forget _Fire(winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher, std::weak_ptr<ThrottledFunc> weakThis)
|
||||
winrt::fire_and_forget _Fire()
|
||||
{
|
||||
co_await winrt::resume_after(delay);
|
||||
co_await winrt::resume_foreground(dispatcher);
|
||||
const auto dispatcher = _dispatcher;
|
||||
auto weakSelf = this->weak_from_this();
|
||||
|
||||
if (auto self{ weakThis.lock() })
|
||||
if constexpr (leading)
|
||||
{
|
||||
std::optional<std::tuple<Args...>> args;
|
||||
co_await winrt::resume_foreground(dispatcher);
|
||||
|
||||
if (auto self{ weakSelf.lock() })
|
||||
{
|
||||
std::lock_guard guard{ self->_lock };
|
||||
self->_pendingRunArgs.swap(args);
|
||||
self->_func();
|
||||
}
|
||||
else
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
std::apply(self->_func, args.value());
|
||||
co_await winrt::resume_after(_delay);
|
||||
|
||||
if (auto self{ weakSelf.lock() })
|
||||
{
|
||||
self->_storage.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
co_await winrt::resume_after(_delay);
|
||||
co_await winrt::resume_foreground(dispatcher);
|
||||
|
||||
if (auto self{ weakSelf.lock() })
|
||||
{
|
||||
std::apply(self->_func, self->_storage.Extract());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Func _func;
|
||||
winrt::Windows::Foundation::TimeSpan _delay;
|
||||
winrt::Windows::UI::Core::CoreDispatcher _dispatcher;
|
||||
winrt::Windows::Foundation::TimeSpan _delay;
|
||||
Func _func;
|
||||
|
||||
std::mutex _lock;
|
||||
std::optional<std::tuple<Args...>> _pendingRunArgs;
|
||||
ThrottledFuncStorage<Args...> _storage;
|
||||
};
|
||||
|
||||
// Class Description:
|
||||
// - Represents a function whose invocation is delayed by a specified duration
|
||||
// and rate-limited such that if the code tries to run the function while a
|
||||
// call to the function is already pending, the request will be ignored.
|
||||
// - The function will be run on the the specified dispatcher.
|
||||
template<>
|
||||
class ThrottledFunc<> : public std::enable_shared_from_this<ThrottledFunc<>>
|
||||
{
|
||||
public:
|
||||
using Func = std::function<void()>;
|
||||
|
||||
ThrottledFunc(Func func, winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher) :
|
||||
_func{ func },
|
||||
_delay{ delay },
|
||||
_dispatcher{ dispatcher }
|
||||
{
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Runs the function later, except if `Run` is called again before
|
||||
// with a new argument, in which case the request will be ignored.
|
||||
// - For more information, read the class' documentation.
|
||||
// - This method is always thread-safe. It can be called multiple times on
|
||||
// different threads.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
template<typename... MakeArgs>
|
||||
void Run(MakeArgs&&... args)
|
||||
{
|
||||
if (!_isRunPending.test_and_set(std::memory_order_relaxed))
|
||||
{
|
||||
_Fire(_delay, _dispatcher, this->weak_from_this());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static winrt::fire_and_forget _Fire(winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher, std::weak_ptr<ThrottledFunc> weakThis)
|
||||
{
|
||||
co_await winrt::resume_after(delay);
|
||||
co_await winrt::resume_foreground(dispatcher);
|
||||
|
||||
if (auto self{ weakThis.lock() })
|
||||
{
|
||||
self->_isRunPending.clear(std::memory_order_relaxed);
|
||||
self->_func();
|
||||
}
|
||||
}
|
||||
|
||||
Func _func;
|
||||
winrt::Windows::Foundation::TimeSpan _delay;
|
||||
winrt::Windows::UI::Core::CoreDispatcher _dispatcher;
|
||||
|
||||
std::atomic_flag _isRunPending;
|
||||
};
|
||||
template<typename... Args>
|
||||
using ThrottledFuncTrailing = ThrottledFunc<false, Args...>;
|
||||
using ThrottledFuncLeading = ThrottledFunc<true>;
|
||||
|
|
Loading…
Reference in a new issue