Skip to content

Commit

Permalink
Split ThrottledFunc into Leading and Trailing variants (#10133)
Browse files Browse the repository at this point in the history
## 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
  • Loading branch information
lhecker authored May 20, 2021
1 parent a8e4bed commit 13f0b8e
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 111 deletions.
32 changes: 16 additions & 16 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() })
{
Expand All @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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.
Expand Down
195 changes: 104 additions & 91 deletions src/cascadia/TerminalControl/ThrottledFunc.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,85 @@ 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
// tries to run the function while a call to the function is already
// 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) }
{
}

Expand All @@ -37,26 +99,16 @@ class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>
// - 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:
Expand All @@ -81,93 +133,54 @@ class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>
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());
}
}

Func _func;
winrt::Windows::Foundation::TimeSpan _delay;
winrt::Windows::UI::Core::CoreDispatcher _dispatcher;

std::mutex _lock;
std::optional<std::tuple<Args...>> _pendingRunArgs;
};

// 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 }
{
}
co_await winrt::resume_after(_delay);

// 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());
if (auto self{ weakSelf.lock() })
{
self->_storage.Reset();
}
}
}

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() })
else
{
self->_isRunPending.clear(std::memory_order_relaxed);
self->_func();
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::atomic_flag _isRunPending;
ThrottledFuncStorage<Args...> _storage;
};

template<typename... Args>
using ThrottledFuncTrailing = ThrottledFunc<false, Args...>;
using ThrottledFuncLeading = ThrottledFunc<true>;

0 comments on commit 13f0b8e

Please sign in to comment.