Skip to content

Commit 13f0b8e

Browse files
authored
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
1 parent a8e4bed commit 13f0b8e

File tree

3 files changed

+124
-111
lines changed

3 files changed

+124
-111
lines changed

src/cascadia/TerminalControl/TermControl.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -110,37 +110,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
110110
// Core eventually won't have access to. When we get to
111111
// https://github.com/microsoft/terminal/projects/5#card-50760282
112112
// then we'll move the applicable ones.
113-
_tsfTryRedrawCanvas = std::make_shared<ThrottledFunc<>>(
113+
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
114+
Dispatcher(),
115+
TsfRedrawInterval,
114116
[weakThis = get_weak()]() {
115117
if (auto control{ weakThis.get() })
116118
{
117119
control->TSFInputControl().TryRedrawCanvas();
118120
}
119-
},
120-
TsfRedrawInterval,
121-
Dispatcher());
121+
});
122122

123-
_updatePatternLocations = std::make_shared<ThrottledFunc<>>(
123+
_updatePatternLocations = std::make_shared<ThrottledFuncTrailing<>>(
124+
Dispatcher(),
125+
UpdatePatternLocationsInterval,
124126
[weakThis = get_weak()]() {
125127
if (auto control{ weakThis.get() })
126128
{
127129
control->_core->UpdatePatternLocations();
128130
}
129-
},
130-
UpdatePatternLocationsInterval,
131-
Dispatcher());
131+
});
132132

133-
_playWarningBell = std::make_shared<ThrottledFunc<>>(
133+
_playWarningBell = std::make_shared<ThrottledFuncLeading>(
134+
Dispatcher(),
135+
TerminalWarningBellInterval,
134136
[weakThis = get_weak()]() {
135137
if (auto control{ weakThis.get() })
136138
{
137139
control->_WarningBellHandlers(*control, nullptr);
138140
}
139-
},
140-
TerminalWarningBellInterval,
141-
Dispatcher());
141+
});
142142

143-
_updateScrollBar = std::make_shared<ThrottledFunc<ScrollBarUpdate>>(
143+
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<ScrollBarUpdate>>(
144+
Dispatcher(),
145+
ScrollBarUpdateInterval,
144146
[weakThis = get_weak()](const auto& update) {
145147
if (auto control{ weakThis.get() })
146148
{
@@ -159,9 +161,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
159161

160162
control->_isInternalScrollBarUpdate = false;
161163
}
162-
},
163-
ScrollBarUpdateInterval,
164-
Dispatcher());
164+
});
165165

166166
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
167167
_autoScrollTimer.Interval(AutoScrollUpdateInterval);

src/cascadia/TerminalControl/TermControl.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
151151
bool _focused;
152152
bool _initializedTerminal;
153153

154-
std::shared_ptr<ThrottledFunc<>> _tsfTryRedrawCanvas;
155-
std::shared_ptr<ThrottledFunc<>> _updatePatternLocations;
156-
std::shared_ptr<ThrottledFunc<>> _playWarningBell;
154+
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
155+
std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations;
156+
std::shared_ptr<ThrottledFuncLeading> _playWarningBell;
157157

158158
struct ScrollBarUpdate
159159
{
@@ -162,7 +162,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
162162
double newMinimum;
163163
double newViewportSize;
164164
};
165-
std::shared_ptr<ThrottledFunc<ScrollBarUpdate>> _updateScrollBar;
165+
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
166166
bool _isInternalScrollBarUpdate;
167167

168168
// Auto scroll occurs when user, while selecting, drags cursor outside viewport. View is then scrolled to 'follow' the cursor.

src/cascadia/TerminalControl/ThrottledFunc.h

Lines changed: 104 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,85 @@ Module Name:
99
#pragma once
1010
#include "pch.h"
1111

12+
template<typename... Args>
13+
class ThrottledFuncStorage
14+
{
15+
public:
16+
template<typename... MakeArgs>
17+
bool Emplace(MakeArgs&&... args)
18+
{
19+
std::scoped_lock guard{ _lock };
20+
21+
const bool hadValue = _pendingRunArgs.has_value();
22+
_pendingRunArgs.emplace(std::forward<MakeArgs>(args)...);
23+
return hadValue;
24+
}
25+
26+
template<typename F>
27+
void ModifyPending(F f)
28+
{
29+
std::scoped_lock guard{ _lock };
30+
31+
if (_pendingRunArgs.has_value())
32+
{
33+
std::apply(f, _pendingRunArgs.value());
34+
}
35+
}
36+
37+
std::tuple<Args...> Extract()
38+
{
39+
decltype(_pendingRunArgs) args;
40+
std::scoped_lock guard{ _lock };
41+
_pendingRunArgs.swap(args);
42+
return args.value();
43+
}
44+
45+
private:
46+
std::mutex _lock;
47+
std::optional<std::tuple<Args...>> _pendingRunArgs;
48+
};
49+
50+
template<>
51+
class ThrottledFuncStorage<>
52+
{
53+
public:
54+
bool Emplace()
55+
{
56+
return _isRunPending.test_and_set(std::memory_order_relaxed);
57+
}
58+
59+
std::tuple<> Extract()
60+
{
61+
Reset();
62+
return {};
63+
}
64+
65+
void Reset()
66+
{
67+
_isRunPending.clear(std::memory_order_relaxed);
68+
}
69+
70+
private:
71+
std::atomic_flag _isRunPending;
72+
};
73+
1274
// Class Description:
1375
// - Represents a function that takes arguments and whose invocation is
1476
// delayed by a specified duration and rate-limited such that if the code
1577
// tries to run the function while a call to the function is already
1678
// pending, then the previous call with the previous arguments will be
1779
// cancelled and the call will be made with the new arguments instead.
1880
// - The function will be run on the the specified dispatcher.
19-
template<typename... Args>
20-
class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>>
81+
template<bool leading, typename... Args>
82+
class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<leading, Args...>>
2183
{
2284
public:
2385
using Func = std::function<void(Args...)>;
2486

25-
ThrottledFunc(Func func, winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher) :
26-
_func{ func },
27-
_delay{ delay },
28-
_dispatcher{ dispatcher }
87+
ThrottledFunc(winrt::Windows::UI::Core::CoreDispatcher dispatcher, winrt::Windows::Foundation::TimeSpan delay, Func func) :
88+
_dispatcher{ std::move(dispatcher) },
89+
_delay{ std::move(delay) },
90+
_func{ std::move(func) }
2991
{
3092
}
3193

@@ -37,26 +99,16 @@ class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>
3799
// - This method is always thread-safe. It can be called multiple times on
38100
// different threads.
39101
// Arguments:
40-
// - arg: the argument to pass to the function
102+
// - args: the arguments to pass to the function
41103
// Return Value:
42104
// - <none>
43105
template<typename... MakeArgs>
44106
void Run(MakeArgs&&... args)
45107
{
108+
if (!_storage.Emplace(std::forward<MakeArgs>(args)...))
46109
{
47-
std::lock_guard guard{ _lock };
48-
49-
bool hadValue = _pendingRunArgs.has_value();
50-
_pendingRunArgs.emplace(std::forward<MakeArgs>(args)...);
51-
52-
if (hadValue)
53-
{
54-
// already pending
55-
return;
56-
}
110+
_Fire();
57111
}
58-
59-
_Fire(_delay, _dispatcher, this->weak_from_this());
60112
}
61113

62114
// Method Description:
@@ -81,93 +133,54 @@ class ThrottledFunc : public std::enable_shared_from_this<ThrottledFunc<Args...>
81133
template<typename F>
82134
void ModifyPending(F f)
83135
{
84-
std::lock_guard guard{ _lock };
85-
86-
if (_pendingRunArgs.has_value())
87-
{
88-
std::apply(f, _pendingRunArgs.value());
89-
}
136+
_storage.ModifyPending(f);
90137
}
91138

92139
private:
93-
static winrt::fire_and_forget _Fire(winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher, std::weak_ptr<ThrottledFunc> weakThis)
140+
winrt::fire_and_forget _Fire()
94141
{
95-
co_await winrt::resume_after(delay);
96-
co_await winrt::resume_foreground(dispatcher);
142+
const auto dispatcher = _dispatcher;
143+
auto weakSelf = this->weak_from_this();
97144

98-
if (auto self{ weakThis.lock() })
145+
if constexpr (leading)
99146
{
100-
std::optional<std::tuple<Args...>> args;
147+
co_await winrt::resume_foreground(dispatcher);
148+
149+
if (auto self{ weakSelf.lock() })
101150
{
102-
std::lock_guard guard{ self->_lock };
103-
self->_pendingRunArgs.swap(args);
151+
self->_func();
152+
}
153+
else
154+
{
155+
co_return;
104156
}
105157

106-
std::apply(self->_func, args.value());
107-
}
108-
}
109-
110-
Func _func;
111-
winrt::Windows::Foundation::TimeSpan _delay;
112-
winrt::Windows::UI::Core::CoreDispatcher _dispatcher;
113-
114-
std::mutex _lock;
115-
std::optional<std::tuple<Args...>> _pendingRunArgs;
116-
};
117-
118-
// Class Description:
119-
// - Represents a function whose invocation is delayed by a specified duration
120-
// and rate-limited such that if the code tries to run the function while a
121-
// call to the function is already pending, the request will be ignored.
122-
// - The function will be run on the the specified dispatcher.
123-
template<>
124-
class ThrottledFunc<> : public std::enable_shared_from_this<ThrottledFunc<>>
125-
{
126-
public:
127-
using Func = std::function<void()>;
128-
129-
ThrottledFunc(Func func, winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher) :
130-
_func{ func },
131-
_delay{ delay },
132-
_dispatcher{ dispatcher }
133-
{
134-
}
158+
co_await winrt::resume_after(_delay);
135159

136-
// Method Description:
137-
// - Runs the function later, except if `Run` is called again before
138-
// with a new argument, in which case the request will be ignored.
139-
// - For more information, read the class' documentation.
140-
// - This method is always thread-safe. It can be called multiple times on
141-
// different threads.
142-
// Arguments:
143-
// - <none>
144-
// Return Value:
145-
// - <none>
146-
template<typename... MakeArgs>
147-
void Run(MakeArgs&&... args)
148-
{
149-
if (!_isRunPending.test_and_set(std::memory_order_relaxed))
150-
{
151-
_Fire(_delay, _dispatcher, this->weak_from_this());
160+
if (auto self{ weakSelf.lock() })
161+
{
162+
self->_storage.Reset();
163+
}
152164
}
153-
}
154-
155-
private:
156-
static winrt::fire_and_forget _Fire(winrt::Windows::Foundation::TimeSpan delay, winrt::Windows::UI::Core::CoreDispatcher dispatcher, std::weak_ptr<ThrottledFunc> weakThis)
157-
{
158-
co_await winrt::resume_after(delay);
159-
co_await winrt::resume_foreground(dispatcher);
160-
161-
if (auto self{ weakThis.lock() })
165+
else
162166
{
163-
self->_isRunPending.clear(std::memory_order_relaxed);
164-
self->_func();
167+
co_await winrt::resume_after(_delay);
168+
co_await winrt::resume_foreground(dispatcher);
169+
170+
if (auto self{ weakSelf.lock() })
171+
{
172+
std::apply(self->_func, self->_storage.Extract());
173+
}
165174
}
166175
}
167176

168-
Func _func;
169-
winrt::Windows::Foundation::TimeSpan _delay;
170177
winrt::Windows::UI::Core::CoreDispatcher _dispatcher;
178+
winrt::Windows::Foundation::TimeSpan _delay;
179+
Func _func;
171180

172-
std::atomic_flag _isRunPending;
181+
ThrottledFuncStorage<Args...> _storage;
173182
};
183+
184+
template<typename... Args>
185+
using ThrottledFuncTrailing = ThrottledFunc<false, Args...>;
186+
using ThrottledFuncLeading = ThrottledFunc<true>;

0 commit comments

Comments
 (0)