Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split ThrottledFunc into Leading and Trailing variants #10133

Merged
2 commits merged into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
lhecker marked this conversation as resolved.
Show resolved Hide resolved
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>;