morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
scheduler.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <chrono>
5#include <functional>
6#include <utility>
7
8/// @file
9/// @brief `morph::reactive::Scheduler`: timers a frontend runs on the runtime's owner executor.
10///
11/// Specified in `docs/spec/reactive/control.md`, "Timed refresh".
12
13namespace morph::reactive {
14
15/// @brief Owns one scheduled timer and cancels it when destroyed. Move-only.
16///
17/// A scheduler hands it two functions: one that cancels the timer and one that says whether the timer is still
18/// scheduled. So `active()` turns false once a one-shot timer has fired, and cancelling a fired timer does nothing.
20public:
21 /// @brief A handle that owns no timer.
22 TimerHandle() = default;
23
24 /// @brief Takes ownership of a timer through the function that cancels it.
25 /// @param cancel Cancels the timer; called at most once. It must tolerate a timer that has already
26 /// fired or whose scheduler is gone.
27 /// @param isScheduled Whether the timer is still scheduled: false once a one-shot timer has fired or its
28 /// scheduler is gone. Empty means the handle cannot tell, and `active()` stays true until `cancel()`.
29 explicit TimerHandle(std::function<void()> cancel, std::function<bool()> isScheduled = {})
30 : _cancel{std::move(cancel)}, _isScheduled{std::move(isScheduled)} {}
31
32 /// @brief Cancels the timer, if this handle still owns one.
34
35 TimerHandle(TimerHandle const&) = delete;
36 TimerHandle& operator=(TimerHandle const&) = delete;
37
38 /// @brief Takes over @p other's timer; @p other then owns none.
39 /// @param other The handle to move from.
40 TimerHandle(TimerHandle&& other) noexcept
41 : _cancel{std::exchange(other._cancel, {})}, _isScheduled{std::exchange(other._isScheduled, {})} {}
42
43 /// @brief Cancels this handle's timer, then takes over @p other's.
44 /// @param other The handle to move from.
45 /// @return `*this`.
46 TimerHandle& operator=(TimerHandle&& other) noexcept {
47 if (this != &other) {
48 cancel();
49 _cancel = std::exchange(other._cancel, {});
50 _isScheduled = std::exchange(other._isScheduled, {});
51 }
52 return *this;
53 }
54
55 /// @brief Cancels the timer, if this handle still owns one.
56 void cancel() noexcept {
57 _isScheduled = {};
58 if (auto const stop = std::exchange(_cancel, {})) {
59 // Runs in the destructor and in move assignment, which cannot report a failure: a cancel function
60 // that breaks its no-throw contract leaves its timer to the scheduler.
61 try {
62 stop();
63 } catch (...) { // NOLINT(bugprone-empty-catch): nothing to report to, as said above
64 }
65 }
66 }
67
68 /// @brief Whether this handle still owns a scheduled timer.
69 /// @return False after `cancel()`, after a move from it, for a default-constructed handle, and once a one-shot
70 /// timer has fired or its scheduler is gone (when the scheduler can say so).
71 [[nodiscard]] bool active() const noexcept {
72 if (!_cancel) {
73 return false;
74 }
75 if (!_isScheduled) {
76 return true;
77 }
78 // A probe that breaks its no-throw contract cannot say the timer is still there.
79 try {
80 return _isScheduled();
81 } catch (...) {
82 return false;
83 }
84 }
85
86private:
87 std::function<void()> _cancel;
88 std::function<bool()> _isScheduled;
89};
90
91/// @brief Runs callbacks after a delay or on a period, on the runtime's owner executor.
92///
93/// Every frontend provides one, on its own event loop's timers; a test uses `testing::ManualScheduler`. Every
94/// implementation guarantees:
95///
96/// - Callbacks run on the owner executor of the `Runtime` the timers serve.
97/// - A one-shot timer's handle reports `active()` false once the timer has fired.
98///
99/// - Once a handle's `cancel()` returns on the owner, its callback never runs, even when its deadline has passed
100/// and a call is already queued.
101/// - `cancel()` may be called from inside a callback, its own timer's included.
102/// - A `TimerHandle` may outlive its `Scheduler`; cancelling it then does nothing.
103/// - `every()` fires once per period while the owner runs. If the owner is blocked past several periods, a
104/// frontend fires once and counts the next deadline from that firing: no burst of missed firings.
105class Scheduler {
106public:
107 Scheduler() = default;
108 virtual ~Scheduler() = default;
109 Scheduler(Scheduler const&) = delete;
110 Scheduler& operator=(Scheduler const&) = delete;
111 Scheduler(Scheduler&&) = delete;
112 Scheduler& operator=(Scheduler&&) = delete;
113
114 /// @brief Runs @p fn once, @p delay from now.
115 /// @param delay How long to wait; a non-positive delay means as soon as possible.
116 /// @param fn The callback.
117 /// @return The handle; destroying it before the deadline cancels the call.
118 [[nodiscard]] virtual TimerHandle after(std::chrono::milliseconds delay, std::function<void()> fn) = 0;
119
120 /// @brief Runs @p fn every @p period, starting one period from now.
121 /// @param period The interval; must be positive.
122 /// @param fn The callback.
123 /// @return The handle; destroying it stops the repetition.
124 /// @throws std::invalid_argument when @p period is not positive.
125 [[nodiscard]] virtual TimerHandle every(std::chrono::milliseconds period, std::function<void()> fn) = 0;
126};
127
128} // namespace morph::reactive
virtual TimerHandle every(std::chrono::milliseconds period, std::function< void()> fn)=0
Runs fn every period, starting one period from now.
virtual TimerHandle after(std::chrono::milliseconds delay, std::function< void()> fn)=0
Runs fn once, delay from now.
Owns one scheduled timer and cancels it when destroyed. Move-only.
Definition scheduler.hpp:19
TimerHandle(std::function< void()> cancel, std::function< bool()> isScheduled={})
Takes ownership of a timer through the function that cancels it.
Definition scheduler.hpp:29
TimerHandle(TimerHandle &&other) noexcept
Takes over other's timer; other then owns none.
Definition scheduler.hpp:40
bool active() const noexcept
Whether this handle still owns a scheduled timer.
Definition scheduler.hpp:71
~TimerHandle()
Cancels the timer, if this handle still owns one.
Definition scheduler.hpp:33
void cancel() noexcept
Cancels the timer, if this handle still owns one.
Definition scheduler.hpp:56
TimerHandle()=default
A handle that owns no timer.
TimerHandle & operator=(TimerHandle &&other) noexcept
Cancels this handle's timer, then takes over other's.
Definition scheduler.hpp:46