Stroika Library 3.0d23x
 
Loading...
Searching...
No Matches
IntervalTimer.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. 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"
17
18#include "IntervalTimer.h"
19
20using namespace Stroika::Foundation;
23using namespace Stroika::Foundation::Execution;
24using namespace Stroika::Foundation::Time;
25
26using Memory::MakeSharedPtr;
27
28/*
29 ********************************************************************************
30 *********************** IntervalTimer::RegisteredTask **************************
31 ********************************************************************************
32 */
34{
36 sb << "{"sv;
37 sb << "Callback: "sv << fCallback;
38 sb << ", CallNextAt: "sv << fCallNextAt;
39 sb << ", Frequency: "sv << fFrequency;
40 sb << ", Hysteresis: "sv << fHysteresis;
41 sb << "}"sv;
42 return sb;
43}
44
45/*
46 ********************************************************************************
47 ******************** IntervalTimer::Manager::DefaultRep ************************
48 ********************************************************************************
49 */
51 Rep_ () = default;
52 virtual ~Rep_ () = default;
53 void AddOneShot (const TimerCallback& intervalTimer, const Time::Duration& when)
54 {
55 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: AddOneShot"};
56 auto lk = fData_.rwget ();
57 lk->Add (RegisteredTask{intervalTimer, Time::GetTickCount () + when});
58 DataChanged_ ();
59 }
60 void AddRepeating (const TimerCallback& intervalTimer, const Time::Duration& repeatInterval, const optional<Time::Duration>& hysteresis)
61 {
62 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: AddRepeating"};
63 auto lk = fData_.rwget ();
64 lk->Add ({intervalTimer, Time::GetTickCount () + repeatInterval, repeatInterval, hysteresis});
65 DataChanged_ ();
66 }
67 void RemoveRepeating (const TimerCallback& intervalTimer) noexcept
68 {
69 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: RemoveRepeating"};
70 auto lk = fData_.rwget ();
71 RegisteredTaskCollection x = lk.cref ();
72 lk->Remove (intervalTimer);
73 DataChanged_ ();
74 }
75 RegisteredTaskCollection GetAllRegisteredTasks () const
76 {
77 Debug::TraceContextBumper ctx{"IntervalTimer::Manager: default implementation: GetAllRegisteredTasks"};
78 return fData_.load ();
79 }
80
81 // @todo - re-implement using priority q, with next time at top of q
84 WaitableEvent fDataChanged_{};
85
86 // this is where a priorityq would be better
87 TimePointSeconds GetNextWakeupTime_ ()
88 {
89 TimePointSeconds funResult =
90 fData_.cget ()->Map<Iterable<TimePointSeconds>> ([] (const RegisteredTask& i) { return i.fCallNextAt; }).MinValue (TimePointSeconds{kInfinity});
91#if qStroika_Foundation_Debug_AssertionsChecked
92 auto dataLock = fData_.cget ();
93 // note: usually (not dataLock->empty ()), but it can be empty temporarily as we are shutting down this process
94 // from one thread, while checking this simultaneously from another
96 for (const RegisteredTask& i : dataLock.cref ()) {
97 r = min (r, i.fCallNextAt);
98 }
99 Assert (r == funResult);
100#endif
101 return funResult;
102 }
103 void RunnerLoop_ ()
104 {
105 // keep checking for timer events to run
106 random_device rd;
107 mt19937 gen{rd ()};
108 while (true) {
109 Require (Debug::AppearsDuringMainLifetime ());
110 fDataChanged_.WaitUntilQuietly (GetNextWakeupTime_ ());
111 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)
112 // now process any timer events that are ready (could easily be more than one).
113 // if we had a priority q, we would do them in order, but for now, just do all that are ready
114 // NOTE - to avoid holding a lock (in case these guys remove themselves or whatever) - lock/run through list twice
115 TimePointSeconds now = Time::GetTickCount ();
116 Collection<RegisteredTask> elts2Run = fData_.cget ()->Where ([=] (const RegisteredTask& i) { return i.fCallNextAt <= now; });
117 // note - this could EASILY be empty, for example, if fDataChanged_ wakes too early due to a change/Signal/Set
118 for (const RegisteredTask& i : elts2Run) {
119 IgnoreExceptionsExceptThreadAbortForCall (i.fCallback ());
120 }
121 // now reset the 'next' time for each run element
122 now = Time::GetTickCount (); // pick 'now' relative to when we finished running tasks
123 auto rwDataLock = fData_.rwget ();
124 for (const RegisteredTask& i : elts2Run) {
125 if (i.fFrequency.has_value ()) {
126 RegisteredTask newE = i;
127 newE.fCallNextAt = now + *i.fFrequency;
128 if (i.fHysteresis) {
129 uniform_real_distribution<> dis{-i.fHysteresis->count (), i.fHysteresis->count ()};
130 newE.fCallNextAt += Time::DurationSeconds{dis (gen)}; // can use fCallNextAt to be called immediately again... or even be < now
131 }
132 rwDataLock->Add (newE); // just replaces
133 }
134 else {
135 rwDataLock->Remove (i);
136 }
137 }
138 }
139 }
140 void DataChanged_ ()
141 {
142 auto rwThreadLk = fThread_.rwget ();
143 if (fData_.cget ()->empty ()) {
144 rwThreadLk.store (nullptr); // destroy thread
145 }
146 else {
147 auto lk = fThread_.rwget ();
148 if (lk.cref () == nullptr) {
149 using namespace Execution;
150 lk.store (MakeSharedPtr<Thread::CleanupPtr> (Thread::CleanupPtr::eAbortBeforeWaiting,
151 Thread::New ([this] () { RunnerLoop_ (); }, Thread::eAutoStart, "Default-Interval-Timer"sv)));
152 }
153 else {
154 fDataChanged_.Set (); // if there was and still is a thread, it maybe sleeping too long, so wake it up
155 }
156 }
157 }
158};
159
160IntervalTimer::Manager::DefaultRep::DefaultRep ()
161 : fHiddenRep_{MakeSharedPtr<Rep_> ()}
162{
163}
164
165void IntervalTimer::Manager::DefaultRep::AddOneShot (const TimerCallback& intervalTimer, const Time::Duration& when)
166{
167 AssertNotNull (fHiddenRep_);
168 fHiddenRep_->AddOneShot (intervalTimer, when);
169}
170
171void IntervalTimer::Manager::DefaultRep::AddRepeating (const TimerCallback& intervalTimer, const Time::Duration& repeatInterval,
172 const optional<Time::Duration>& hysteresis)
173{
174 AssertNotNull (fHiddenRep_);
175 fHiddenRep_->AddRepeating (intervalTimer, repeatInterval, hysteresis);
176}
177
178void IntervalTimer::Manager::DefaultRep::RemoveRepeating (const TimerCallback& intervalTimer) noexcept
179{
180 AssertNotNull (fHiddenRep_);
181 fHiddenRep_->RemoveRepeating (intervalTimer);
182}
183
184auto IntervalTimer::Manager::DefaultRep::GetAllRegisteredTasks () const -> RegisteredTaskCollection
185{
186 AssertNotNull (fHiddenRep_);
187 return fHiddenRep_->GetAllRegisteredTasks ();
188}
189
190/*
191 ********************************************************************************
192 ********************* IntervalTimer::Manager::Activator ************************
193 ********************************************************************************
194 */
195IntervalTimer::Manager::Activator::Activator ()
196{
197 Debug::TraceContextBumper ctx{"IntervalTimer::Manager::Activator::Activator"};
198 Require (Manager::sThe.fRep_ == nullptr); // only one activator object allowed
199 Require (Debug::AppearsDuringMainLifetime ());
200 Manager::sThe = Manager{MakeSharedPtr<IntervalTimer::Manager::DefaultRep> ()};
201}
202
203IntervalTimer::Manager::Activator::~Activator ()
204{
205 Debug::TraceContextBumper ctx{"IntervalTimer::Manager::Activator::~Activator"};
206 RequireNotNull (Manager::sThe.fRep_); // this is the only way to remove, and so must not be null here
207 Require (Debug::AppearsDuringMainLifetime ());
208 Manager::sThe.fRep_.reset ();
209}
210
211/*
212 ********************************************************************************
213 ***************************** IntervalTimer::Adder *****************************
214 ********************************************************************************
215 */
216IntervalTimer::Adder::Adder (IntervalTimer::Manager& manager, const Function<void (void)>& f, const Time::Duration& repeatInterval,
217 RunImmediatelyFlag runImmediately, const optional<Time::Duration>& hysteresis)
218 : fRepeatInterval_{repeatInterval}
219 , fHysteresis_{hysteresis}
220 , fManager_{&manager}
221 , fFunction_{f}
222{
223 Manager::sThe.AddRepeating (fFunction_, repeatInterval, hysteresis);
224 if (runImmediately == RunImmediatelyFlag::eRunImmediately) {
225 IgnoreExceptionsExceptThreadAbortForCall (fFunction_ ());
226 }
227}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
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 ...
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:960