Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
IntervalTimer.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <random>
7
10#include "Stroika/Foundation/Containers/Collection.h"
11#include "Stroika/Foundation/Debug/Main.h"
16
17#include "IntervalTimer.h"
18
19using namespace Stroika::Foundation;
22using namespace Stroika::Foundation::Execution;
23using namespace Stroika::Foundation::Time;
24
25/*
26 ********************************************************************************
27 *********************** IntervalTimer::RegisteredTask **************************
28 ********************************************************************************
29 */
31{
33 sb << "{"sv;
34 sb << "Callback: "sv << fCallback;
35 sb << ", CallNextAt: "sv << fCallNextAt;
36 sb << ", Frequency: "sv << fFrequency;
37 sb << ", Hysteresis: "sv << fHysteresis;
38 sb << "}"sv;
39 return sb;
40}
41
42/*
43 ********************************************************************************
44 ******************** IntervalTimer::Manager::DefaultRep ************************
45 ********************************************************************************
46 */
48 Rep_ () = default;
49 virtual ~Rep_ () = default;
50 void AddOneShot (const TimerCallback& intervalTimer, const Time::Duration& when)
51 {
52 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: AddOneShot"};
53 auto lk = fData_.rwget ();
54 lk->Add (RegisteredTask{intervalTimer, Time::GetTickCount () + when});
55 DataChanged_ ();
56 }
57 void AddRepeating (const TimerCallback& intervalTimer, const Time::Duration& repeatInterval, const optional<Time::Duration>& hysteresis)
58 {
59 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: AddRepeating"};
60 auto lk = fData_.rwget ();
61 lk->Add ({intervalTimer, Time::GetTickCount () + repeatInterval, repeatInterval, hysteresis});
62 DataChanged_ ();
63 }
64 void RemoveRepeating (const TimerCallback& intervalTimer) noexcept
65 {
66 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: RemoveRepeating"};
67 auto lk = fData_.rwget ();
68 RegisteredTaskCollection x = lk.cref ();
69 lk->Remove (intervalTimer);
70 DataChanged_ ();
71 }
72 RegisteredTaskCollection GetAllRegisteredTasks () const
73 {
74 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: GetAllRegisteredTasks"};
75 return fData_.load ();
76 }
77
78 // @todo - re-implement using priority q, with next time at top of q
81 WaitableEvent fDataChanged_{};
82
83 // this is where a priorityq would be better
84 TimePointSeconds GetNextWakeupTime_ ()
85 {
86 TimePointSeconds funResult =
87 fData_.cget ()->Map<Iterable<TimePointSeconds>> ([] (const RegisteredTask& i) { return i.fCallNextAt; }).MinValue (TimePointSeconds{kInfinity});
88#if qStroika_Foundation_Debug_AssertionsChecked
89 auto dataLock = fData_.cget ();
90 // note: usually (not dataLock->empty ()), but it can be empty temporarily as we are shutting down this process
91 // from one thread, while checking this simultaneously from another
93 for (const RegisteredTask& i : dataLock.cref ()) {
94 r = min (r, i.fCallNextAt);
95 }
96 Assert (r == funResult);
97#endif
98 return funResult;
99 }
100 void RunnerLoop_ ()
101 {
102 // keep checking for timer events to run
103 random_device rd;
104 mt19937 gen{rd ()};
105 while (true) {
106 Require (Debug::AppearsDuringMainLifetime ());
107 fDataChanged_.WaitUntilQuietly (GetNextWakeupTime_ ());
108 fDataChanged_.Reset (); // SUBTLE why this is not a race. Yes, another thread could post new data, but we haven't yet looked through the data so OK; and only one reader (us)
109 // now process any timer events that are ready (could easily be more than one).
110 // if we had a priority q, we would do them in order, but for now, just do all that are ready
111 // NOTE - to avoid holding a lock (in case these guys remove themselves or whatever) - lock/run through list twice
112 TimePointSeconds now = Time::GetTickCount ();
113 Collection<RegisteredTask> elts2Run = fData_.cget ()->Where ([=] (const RegisteredTask& i) { return i.fCallNextAt <= now; });
114 // note - this could EASILY be empty, for example, if fDataChanged_ wakes too early due to a change/Signal/Set
115 for (const RegisteredTask& i : elts2Run) {
116 IgnoreExceptionsExceptThreadAbortForCall (i.fCallback ());
117 }
118 // now reset the 'next' time for each run element
119 now = Time::GetTickCount (); // pick 'now' relative to when we finished running tasks
120 auto rwDataLock = fData_.rwget ();
121 for (const RegisteredTask& i : elts2Run) {
122 if (i.fFrequency.has_value ()) {
123 RegisteredTask newE = i;
124 newE.fCallNextAt = now + *i.fFrequency;
125 if (i.fHysteresis) {
126 uniform_real_distribution<> dis{-i.fHysteresis->count (), i.fHysteresis->count ()};
127 newE.fCallNextAt += Time::DurationSeconds{dis (gen)}; // can use fCallNextAt to be called immediately again... or even be < now
128 }
129 rwDataLock->Add (newE); // just replaces
130 }
131 else {
132 rwDataLock->Remove (i);
133 }
134 }
135 }
136 }
137 void DataChanged_ ()
138 {
139 auto rwThreadLk = fThread_.rwget ();
140 if (fData_.cget ()->empty ()) {
141 rwThreadLk.store (nullptr); // destroy thread
142 }
143 else {
144 auto lk = fThread_.rwget ();
145 if (lk.cref () == nullptr) {
146 using namespace Execution;
147 lk.store (make_shared<Thread::CleanupPtr> (Thread::CleanupPtr::eAbortBeforeWaiting,
148 Thread::New ([this] () { RunnerLoop_ (); }, Thread::eAutoStart, "Default-Interval-Timer"sv)));
149 }
150 else {
151 fDataChanged_.Set (); // if there was and still is a thread, it maybe sleeping too long, so wake it up
152 }
153 }
154 }
155};
156
157IntervalTimer::Manager::DefaultRep::DefaultRep ()
158 : fHiddenRep_{make_shared<Rep_> ()}
159{
160}
161
162void IntervalTimer::Manager::DefaultRep::AddOneShot (const TimerCallback& intervalTimer, const Time::Duration& when)
163{
164 AssertNotNull (fHiddenRep_);
165 fHiddenRep_->AddOneShot (intervalTimer, when);
166}
167
168void IntervalTimer::Manager::DefaultRep::AddRepeating (const TimerCallback& intervalTimer, const Time::Duration& repeatInterval,
169 const optional<Time::Duration>& hysteresis)
170{
171 AssertNotNull (fHiddenRep_);
172 fHiddenRep_->AddRepeating (intervalTimer, repeatInterval, hysteresis);
173}
174
175void IntervalTimer::Manager::DefaultRep::RemoveRepeating (const TimerCallback& intervalTimer) noexcept
176{
177 AssertNotNull (fHiddenRep_);
178 fHiddenRep_->RemoveRepeating (intervalTimer);
179}
180
181auto IntervalTimer::Manager::DefaultRep::GetAllRegisteredTasks () const -> RegisteredTaskCollection
182{
183 AssertNotNull (fHiddenRep_);
184 return fHiddenRep_->GetAllRegisteredTasks ();
185}
186
187/*
188 ********************************************************************************
189 ********************* IntervalTimer::Manager::Activator ************************
190 ********************************************************************************
191 */
192IntervalTimer::Manager::Activator::Activator ()
193{
194 Debug::TraceContextBumper ctx{"IntervalTimer::Manager::Activator::Activator"};
195 Require (Manager::sThe.fRep_ == nullptr); // only one activator object allowed
196 Require (Debug::AppearsDuringMainLifetime ());
197 Manager::sThe = Manager{make_shared<IntervalTimer::Manager::DefaultRep> ()};
198}
199
200IntervalTimer::Manager::Activator::~Activator ()
201{
202 Debug::TraceContextBumper ctx{"IntervalTimer::Manager::Activator::~Activator"};
203 RequireNotNull (Manager::sThe.fRep_); // this is the only way to remove, and so must not be null here
204 Require (Debug::AppearsDuringMainLifetime ());
205 Manager::sThe.fRep_.reset ();
206}
207
208/*
209 ********************************************************************************
210 ***************************** IntervalTimer::Adder *****************************
211 ********************************************************************************
212 */
213IntervalTimer::Adder::Adder (IntervalTimer::Manager& manager, const Function<void (void)>& f, const Time::Duration& repeatInterval,
214 RunImmediatelyFlag runImmediately, const optional<Time::Duration>& hysteresis)
215 : fRepeatInterval_{repeatInterval}
216 , fHysteresis_{hysteresis}
217 , fManager_{&manager}
218 , fFunction_{f}
219{
220 Manager::sThe.AddRepeating (fFunction_, repeatInterval, hysteresis);
221 if (runImmediately == RunImmediatelyFlag::eRunImmediately) {
222 IgnoreExceptionsExceptThreadAbortForCall (fFunction_ ());
223 }
224}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
time_point< RealtimeClock, DurationSeconds > TimePointSeconds
TimePointSeconds is a simpler approach to chrono::time_point, which doesn't require using templates e...
Definition Realtime.h:82
constexpr DurationSeconds kInfinity
Definition Realtime.h:104
chrono::duration< double > DurationSeconds
chrono::duration<double> - a time span (length of time) measured in seconds, but high precision.
Definition Realtime.h:57
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
Definition Collection.h:102
nonvirtual void AddRepeating(const TimerCallback &intervalTimer, const Time::Duration &repeatInterval, const optional< Time::Duration > &hysteresis=nullopt)
Add a timer to be called repeatedly after duration repeatInterval.
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
nonvirtual WritableReference rwget()
get a read-write smart pointer to the underlying Synchronized<> object, holding the full lock the who...
nonvirtual ReadableReference cget() const
get a read-only smart pointer to the underlying Synchronized<> object, holding the readlock the whole...
Duration is a chrono::duration<double> (=.
Definition Duration.h:96
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955