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:
Leonard Hecker 2021-05-20 19:31:47 +02:00 committed by GitHub
parent a8e4bedae3
commit 13f0b8e007
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 126 additions and 113 deletions

View file

@ -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);

View file

@ -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.

View file

@ -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>;