Sparkle 0.0.1
Loading...
Searching...
No Matches
spk_contract_provider.hpp
1#pragma once
2
3#include <algorithm>
4#include <functional>
5#include <memory>
6#include <mutex>
7#include <optional>
8#include <stdexcept>
9#include <tuple>
10#include <type_traits>
11#include <utility>
12#include <vector>
13
14namespace spk
15{
27 template <typename... TParameterTypes>
28 class TContractProvider
29 {
30 private:
31 struct State
32 {
33 TContractProvider *provider = nullptr;
34 };
35
36 public:
41 class Contract
42 {
43 friend class TContractProvider<TParameterTypes...>;
44
45 public:
47 using Job = std::function<void(TParameterTypes...)>;
48
49 private:
50 std::shared_ptr<Job> _job = nullptr;
51 std::shared_ptr<State> _state;
52
53 Contract(TContractProvider *p_originator, const std::shared_ptr<Job> &p_job) :
54 _job(p_job),
55 _state(p_originator ? p_originator->_state : nullptr)
56 {
57 }
58
59 public:
60 Contract() = default;
61
62 Contract(const Contract &) = delete;
63 Contract &operator=(const Contract &) = delete;
64
69 Contract(Contract &&p_other) noexcept :
70 _job(std::move(p_other._job)),
71 _state(std::move(p_other._state))
72 {
73 p_other._job.reset();
74 p_other._state.reset();
75 }
76
82 Contract &operator=(Contract &&p_other) noexcept
83 {
84 if (this != &p_other)
85 {
86 if (isValid())
87 {
88 resign();
89 }
90 _job = std::move(p_other._job);
91 _state = std::move(p_other._state);
92 p_other._job.reset();
93 p_other._state.reset();
94 }
95 return (*this);
96 }
97
100 {
101 if (isValid())
102 {
103 resign();
104 }
105 }
106
111 bool isValid() const
112 {
113 return (_job && *_job);
114 }
115
120 void resign()
121 {
122 if (isValid() == false)
123 {
124 throw std::runtime_error("Can't resign an already resigned contract.");
125 }
126 *_job = nullptr;
127
128 if (_state && _state->provider)
129 {
130 _state->provider->unsubscribe(*this);
131 }
132
133 _job.reset();
134 _state.reset();
135 }
136
142 {
143 if (isValid() == false)
144 {
145 throw std::runtime_error("Can't relinquish an already resigned contract.");
146 }
147 if (_state && _state->provider)
148 {
149 _state->provider->relinquish(std::move(*this));
150 }
151 _job.reset();
152 _state.reset();
153 }
154
159 void trigger(TParameterTypes... p_args) const
160 {
161 if (isValid())
162 {
163 (*_job)(std::forward<TParameterTypes>(p_args)...);
164 }
165 }
166 };
167
172 class Blocker
173 {
174 friend class TContractProvider<TParameterTypes...>;
175
176 public:
178 enum class Mode
179 {
180 Ignore = 0,
181 Delay = 1,
182 };
183
184 Blocker() = default;
185
186 Blocker(const Blocker &) = delete;
187 Blocker &operator=(const Blocker &) = delete;
188
193 Blocker(Blocker &&p_other) noexcept :
194 _state(std::move(p_other._state)),
195 _mode(p_other._mode)
196 {
197 }
198
204 Blocker &operator=(Blocker &&p_other) noexcept
205 {
206 if (this != &p_other)
207 {
208 release();
209 _state = std::move(p_other._state);
210 _mode = p_other._mode;
211 }
212 return *this;
213 }
214
215 ~Blocker()
216 {
217 release();
218 }
219
224 bool active() const noexcept
225 {
226 return (_state && _state->provider != nullptr);
227 }
228
233 Mode mode() const noexcept
234 {
235 return _mode;
236 }
237
241 void release() noexcept
242 {
243 auto p_state = std::move(_state);
244 if (!p_state)
245 {
246 return;
247 }
248
249 if (auto *prov = p_state->provider)
250 {
251 prov->_releaseBlock(_mode);
252 }
253 }
254
255 private:
256 std::shared_ptr<State> _state;
257 Mode _mode = Mode::Ignore;
258
259 Blocker(std::shared_ptr<State> p_state, Mode p_mode) :
260 _state(std::move(p_state)),
261 _mode(p_mode)
262 {
263 }
264 };
265
267 using Job = typename Contract::Job;
269 using VoidJob = std::function<void()>;
270
271 private:
272 std::vector<std::shared_ptr<Job>> _subscribedJobs;
273 std::vector<Contract> _relinquishedContracts;
274
275 std::shared_ptr<State> _state = std::make_shared<State>(State{this});
276
277 mutable std::recursive_mutex _mutex;
278
279 mutable std::size_t _ignoreCount = 0;
280 mutable std::size_t _delayCount = 0;
281
282 using ArgTuple = std::tuple<std::decay_t<TParameterTypes>...>;
283 mutable std::optional<ArgTuple> _pendingDelayed;
284
285 public:
286 TContractProvider() = default;
287
292 {
294
295 if (_state)
296 {
297 _state->provider = nullptr;
298 }
299 }
300
301 TContractProvider(const TContractProvider &) = delete;
302 TContractProvider &operator=(const TContractProvider &) = delete;
303
308 TContractProvider(TContractProvider &&p_other) noexcept
309 {
310 std::lock_guard<std::recursive_mutex> lock(p_other._mutex);
311
312 _subscribedJobs = std::move(p_other._subscribedJobs);
313 _relinquishedContracts = std::move(p_other._relinquishedContracts);
314 _state = std::move(p_other._state);
315 _ignoreCount = p_other._ignoreCount;
316 _delayCount = p_other._delayCount;
317 _pendingDelayed = std::move(p_other._pendingDelayed);
318
319 if (_state)
320 {
321 _state->provider = this;
322 }
323
324 p_other._subscribedJobs.clear();
325 p_other._relinquishedContracts.clear();
326 p_other._ignoreCount = 0;
327 p_other._delayCount = 0;
328 p_other._pendingDelayed = std::nullopt;
329 p_other._state = std::make_shared<State>(State{&p_other});
330 }
331
337 TContractProvider &operator=(TContractProvider &&p_other) noexcept
338 {
339 if (this != &p_other)
340 {
341 std::scoped_lock lock(_mutex, p_other._mutex);
342
344 if (_state)
345 {
346 _state->provider = nullptr;
347 }
348
349 _subscribedJobs = std::move(p_other._subscribedJobs);
350 _relinquishedContracts = std::move(p_other._relinquishedContracts);
351 _state = std::move(p_other._state);
352 _ignoreCount = p_other._ignoreCount;
353 _delayCount = p_other._delayCount;
354 _pendingDelayed = std::move(p_other._pendingDelayed);
355
356 if (_state)
357 {
358 _state->provider = this;
359 }
360
361 p_other._subscribedJobs.clear();
362 p_other._relinquishedContracts.clear();
363 p_other._ignoreCount = 0;
364 p_other._delayCount = 0;
365 p_other._pendingDelayed = std::nullopt;
366 p_other._state = std::make_shared<State>(State{&p_other});
367 }
368 return (*this);
369 }
370
376 Blocker block(typename Blocker::Mode p_mode = Blocker::Mode::Ignore)
377 {
378 std::lock_guard<std::recursive_mutex> lock(_mutex);
379 _applyBlock(p_mode);
380 return Blocker(_state, p_mode);
381 }
382
387 {
388 std::lock_guard<std::recursive_mutex> lock(_mutex);
389 for (auto &job : _subscribedJobs)
390 {
391 if (job)
392 {
393 *job = nullptr;
394 }
395 }
396 _subscribedJobs.clear();
397 _relinquishedContracts.clear();
398 _pendingDelayed.reset();
399 }
400
406 Contract subscribe(const Job &p_job)
407 {
408 std::lock_guard<std::recursive_mutex> lock(_mutex);
409 auto toAdd = std::make_shared<Job>(p_job);
410 _subscribedJobs.push_back(toAdd);
411 return Contract(this, toAdd);
412 }
413
414 template <typename Dummy = void, typename = std::enable_if_t<(sizeof...(TParameterTypes) > 0), Dummy>>
420 Contract subscribe(const VoidJob &p_job)
421 {
422 return subscribe([p_job](TParameterTypes...) {
423 p_job();
424 });
425 }
426
431 void relinquish(Contract &&p_contract)
432 {
433 std::lock_guard<std::recursive_mutex> lock(_mutex);
434 _relinquishedContracts.push_back(std::move(p_contract));
435 }
436
441 void unsubscribe(const Contract &p_contract)
442 {
443 std::lock_guard<std::recursive_mutex> lock(_mutex);
444 auto it = std::remove(_subscribedJobs.begin(), _subscribedJobs.end(), p_contract._job);
445 if (it != _subscribedJobs.end())
446 {
447 if (*it && **it)
448 {
449 **it = nullptr;
450 }
451 _subscribedJobs.erase(it, _subscribedJobs.end());
452 }
453 }
454
459 void trigger(TParameterTypes... p_args) const
460 {
461 std::lock_guard<std::recursive_mutex> lock(_mutex);
462
463 if (_delayCount > 0)
464 {
465 _pendingDelayed.emplace(std::forward<TParameterTypes>(p_args)...);
466 return;
467 }
468
469 if (_ignoreCount > 0)
470 {
471 return;
472 }
473
474 for (auto &job : _subscribedJobs)
475 {
476 if (job && *job)
477 {
478 (*job)(std::forward<TParameterTypes>(p_args)...);
479 }
480 }
481 }
482
487 size_t nbContracts() const
488 {
489 return (_subscribedJobs.size());
490 }
491
492 private:
493 void _applyBlock(typename Blocker::Mode p_mode)
494 {
495 switch (p_mode)
496 {
498 {
499 ++_ignoreCount;
500 _pendingDelayed.reset();
501 break;
502 }
504 {
505 ++_delayCount;
506 break;
507 }
508 }
509 }
510
511 void _releaseBlock(typename Blocker::Mode p_mode)
512 {
513 std::lock_guard<std::recursive_mutex> lock(_mutex);
514
515 if (p_mode == Blocker::Mode::Ignore)
516 {
517 if (_ignoreCount > 0)
518 {
519 --_ignoreCount;
520 }
521 return;
522 }
523
524 if (_delayCount > 0)
525 {
526 --_delayCount;
527 }
528
529 if (_ignoreCount == 0 && _delayCount == 0 && _pendingDelayed.has_value())
530 {
531 auto args = std::move(*_pendingDelayed);
532
533 _pendingDelayed.reset();
534
535 std::apply([this](auto &&...p_xs) {
536 this->trigger(std::forward<decltype(p_xs)>(p_xs)...);
537 },
538 std::move(args));
539 }
540 }
541 };
542
543 using ContractProvider = TContractProvider<>;
544}
RAII helper that ignores or delays triggers while active.
Definition spk_contract_provider.hpp:173
void release() noexcept
Releases the block, applying deferred triggers when appropriate.
Definition spk_contract_provider.hpp:241
bool active() const noexcept
Checks if this blocker is attached to a provider.
Definition spk_contract_provider.hpp:224
Mode
Strategies controlling how callbacks are treated while blocked.
Definition spk_contract_provider.hpp:179
@ Delay
Definition spk_contract_provider.hpp:181
@ Ignore
Definition spk_contract_provider.hpp:180
Blocker & operator=(Blocker &&p_other) noexcept
Move-assigns a blocker, releasing current ownership first.
Definition spk_contract_provider.hpp:204
Mode mode() const noexcept
Gets the blocking mode.
Definition spk_contract_provider.hpp:233
Blocker(Blocker &&p_other) noexcept
Move-constructs a blocker, transferring block ownership.
Definition spk_contract_provider.hpp:193
Handle to a subscribed job that can be resigned or triggered.
Definition spk_contract_provider.hpp:42
std::function< void(TParameterTypes...)> Job
Callback signature accepted by the provider.
Definition spk_contract_provider.hpp:47
bool isValid() const
Checks whether the contract currently owns a valid job.
Definition spk_contract_provider.hpp:111
~Contract()
Resigns the contract upon destruction when still subscribed.
Definition spk_contract_provider.hpp:99
Contract(Contract &&p_other) noexcept
Move-constructs the contract, transferring ownership.
Definition spk_contract_provider.hpp:69
void trigger(TParameterTypes... p_args) const
Directly invokes the job if valid.
Definition spk_contract_provider.hpp:159
Contract & operator=(Contract &&p_other) noexcept
Move-assigns the contract, resigning any current subscription.
Definition spk_contract_provider.hpp:82
void relinquish()
Transfers ownership back to the provider without immediate unsubscribe.
Definition spk_contract_provider.hpp:141
void resign()
Unsubscribes the contract and drops ownership.
Definition spk_contract_provider.hpp:120
Publishes callbacks (contracts) and allows blocking, delaying, or triggering them with arguments.
Definition spk_contract_provider.hpp:29
void invalidateContracts()
Invalidates and clears all contracts.
Definition spk_contract_provider.hpp:386
size_t nbContracts() const
Returns the number of currently subscribed contracts.
Definition spk_contract_provider.hpp:487
void trigger(TParameterTypes... p_args) const
Triggers all subscribed jobs unless blocked/delayed.
Definition spk_contract_provider.hpp:459
TContractProvider(TContractProvider &&p_other) noexcept
Move-constructs, transferring subscriptions and state.
Definition spk_contract_provider.hpp:308
~TContractProvider()
Destroys the provider and invalidates all contracts.
Definition spk_contract_provider.hpp:291
std::function< void()> VoidJob
Callback used for providers without parameters.
Definition spk_contract_provider.hpp:269
Contract subscribe(const VoidJob &p_job)
Subscribes a job without parameters, wrapping it to match the signature.
Definition spk_contract_provider.hpp:420
TContractProvider & operator=(TContractProvider &&p_other) noexcept
Move-assigns, releasing current contracts and adopting the source state.
Definition spk_contract_provider.hpp:337
void unsubscribe(const Contract &p_contract)
Unsubscribes a contract, removing it from the list.
Definition spk_contract_provider.hpp:441
Contract subscribe(const Job &p_job)
Subscribes a job and returns its contract handle.
Definition spk_contract_provider.hpp:406
typename Contract::Job Job
Alias to the subscribed callback signature.
Definition spk_contract_provider.hpp:267
Blocker block(typename Blocker::Mode p_mode=Blocker::Mode::Ignore)
Creates a blocker in the given mode.
Definition spk_contract_provider.hpp:376
void relinquish(Contract &&p_contract)
Moves a contract into the relinquished list (no immediate unsubscribe).
Definition spk_contract_provider.hpp:431
STL namespace.