@@ -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{
2284public:
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
92139private:
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