Stroika Library 3.0d23
 
Loading...
Searching...
No Matches
ModuleGetterSetter.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Execution_ModuleGetterSetter_h_
5#define _Stroika_Foundation_Execution_ModuleGetterSetter_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <optional>
10
11#include "Stroika/Foundation/Common/Common.h"
12#include "Stroika/Foundation/Memory/Common.h"
13
14/**
15 * \note Code-Status: <a href="Code-Status.md#Alpha">Alpha</a>
16 *
17 * \todo add Concept for IMPL type so clear documentation on how to use.
18 */
19
21
22 /**
23 * Argument IMPL type appears to be a valid ModuleGetterSetter set of functions to go with return type T.
24 *
25 * @tparam T
26 * @tparam IMPL
27 *
28 * \note - we might want to remove the requirement that IMPL be copyable, but thats true of our current
29 * implementation.
30 */
31 template <typename IMPL, typename T>
32 concept IModuleGetterSetterImpl = copyable<IMPL> and requires (IMPL impl) {
33 { impl.Get () } -> same_as<T>;
34 { impl.Set (declval<T> ()) } -> same_as<void>;
35 };
36
37 /**
38 * \brief Helper to define synchronized, lazy constructed, module initialization (intended to work with DataExchange::OptionFile)
39 *
40 * Features:
41 * o Simple API - get/set
42 * o automatically intrinsically threadsafe
43 * o Init underling object on first access, so easy to declare globally (static init) and less worry about running before main
44 * o IMPL need not worry about thread safety. Just init on CTOR, and implement Get/Set methods.
45 *
46 * Why use this instead of std c++ static inline object initialization?
47 * o This provides internal synchronization for reads/writes (std c++ object initialization only guarantees threadsafe initialization
48 * across modules)
49 * o Lazy construction - so underlying object construction (intended for the case where this is costly, like read/parse files)
50 *
51 * \par Example Usage
52 * \code
53 * struct MyData_ {
54 * bool fEnabled = false;
55 * optional<DateTime> fLastSynchronizedAt;
56 * };
57 * struct ModuleGetterSetter_Implementation_MyData_ {
58 * ModuleGetterSetter_Implementation_MyData_ ()
59 * : fOptionsFile_ {
60 * "MyModule",
61 * [] () -> ObjectVariantMapper {
62 * ObjectVariantMapper mapper;
63 * mapper.AddClass<MyData_> ({
64 * { "Enabled", &MyData_::fEnabled },
65 * { "Last-Synchronized-At", &MyData_::fLastSynchronizedAt },
66 * });
67 * return mapper;
68 * } ()
69 * , OptionsFile::kDefaultUpgrader
70 * , OptionsFile::mkFilenameMapper ("Put-Your-App-Name-Here")
71 * }
72 * , fActualCurrentConfigData_{fOptionsFile_.Read<MyData_> (MyData_{})}
73 * {
74 * Set (fActualCurrentConfigData_); // assure derived data (and changed fields etc) up to date
75 * }
76 * MyData_ Get () const
77 * {
78 * return fActualCurrentConfigData_;
79 * }
80 * void Set (const MyData_& v)
81 * {
82 * fActualCurrentConfigData_ = v;
83 * fOptionsFile_.Write (v);
84 * }
85 * private:
86 * OptionsFile fOptionsFile_;
87 * MyData_ fActualCurrentConfigData_; // automatically initialized just in time, and externally synchronized
88 * };
89 *
90 * using Execution::ModuleGetterSetter;
91 * ModuleGetterSetter<MyData_, ModuleGetterSetter_Implementation_MyData_> sModuleConfiguration_;
92 *
93 * void TestUse_ ()
94 * {
95 * if (sModuleConfiguration_.Get ().fEnabled) {
96 * auto n = sModuleConfiguration_.Get ();
97 * n.fEnabled = false; // change something in 'n' here
98 * sModuleConfiguration_.Set (n);
99 * }
100 * }
101 * void TestUse3_ ()
102 * {
103 * if (sModuleConfiguration_.Update ([](const MyData_& data) -> optional<MyData_> { if (data.fLastSynchronizedAt && *data.fLastSynchronizedAt + kMinTime_ > DateTime::Now ()) { MyData_ result = data; result.fLastSynchronizedAt = DateTime::Now (); return result; } return {}; })) {
104 * sWaitableEvent.Set (); // e.g. trigger someone to wakeup and used changes? - no global lock held here...
105 * }
106 * }
107 * \endcode
108 *
109 * \note \em Thread-Safety <a href="Thread-Safety.md#Internally-Synchronized-Thread-Safety">Internally-Synchronized-Thread-Safety</a>
110 */
111 template <typename T, IModuleGetterSetterImpl<T> IMPL>
113 public:
114 /**
115 * Assures the getter/setter initial load is done. Same as 'Get', but doesn't return a value.
116 * The function exists mostly for usage clarity sake.
117 *
118 * \note this method may fail (exception).
119 */
120 nonvirtual void AssureLoaded () const;
121
122 public:
123 /**
124 * Grab the global value, performing any necessary read-locks automatically.
125 *
126 * \note - this returns a copy of T, not a reference, in order to properly manage locking, though
127 * since its inlined, for most cases, much of that case may be optimized away (the most expensive
128 * unavoidable piece being the read lock).
129 */
130 nonvirtual T Get () const;
131
132 public:
133 /**
134 * Set the global value, performing any necessary write-locks automatically.
135 */
136 nonvirtual void Set (const T& v);
137
138 public:
139 /**
140 * Experimental API as of 2024-02-22 - if works well - store shared_ptr internally - now SharedByValue - and then can return
141 * its shared_ptr - safely - always readonly? Anyhow - COULD be done so returned shared_ptr not constructed all the time... But still
142 * lock safety stuff...
143 *
144 * \note this API fully threadsafe - as it returns a shared_ptr to data that is a snapshot as of when called if the wrapped data.
145 * If some thread does a write - doesn't affect the returned data here.
146 */
147 nonvirtual shared_ptr<const T> operator->() const;
148
149 public:
150 /**
151 * \brief Call this with a lambda that will update the associated value (INSIDE a lock (synchronized))
152 *
153 * Call this with a lambda that will update the associated value. The update will happen INSIDE
154 * a lock (synchronized). {{ reconsider if we should make that explicit promise or upgradelock
155 * so we can do update and check for change inside a shared_lock and never write lock if no change}}
156 *
157 * updaterFunction should return nullopt if no change, or the new value if changed.
158 *
159 * Update () assures atomic update of your global data, and returns copy of the new value set (optional - this can be ignored).
160 * But since it returns an optional<> you can test the result to see if any update was made, and trigger a wakeup or
161 * further processing (without the global lock held).
162 *
163 * \par Example Usage
164 * \code
165 * sModuleConfiguration_.Update ([] (MyData_ data) { if (data.fLastSynchronizedAt + kMinTime_ > DateTime::Now ()) { data.fLastSynchronizedAt = DateTime::Now ();} return data; });
166 * \endcode
167 *
168 * \par Example Usage
169 * \code
170 * if (sModuleConfiguration_.Update ([] (const MyData_& data) -> optional<MyData_> { if (data.fLastSynchronizedAt + kMinTime_ > DateTime::Now ()) { MyData_ result = data; result.fLastSynchronizedAt = DateTime::Now (); return result;} return {}; })) {
171 * sWaitableEvent.Set (); // e.g. trigger someone to wakeup and used changes? - no global lock held here...
172 * }
173 * \endcode
174 */
175 nonvirtual optional<T> Update (const function<optional<T> (const T&)>& updaterFunction);
176
177 private:
178 // Declare 'Get' method as const since it doesn't CONCEPTUALLY modify the object, but in fact it does. Works better for c++
179 // standard thread safety rules (const unsynchronized) - since this object internally synchronized.
180 mutable RWSynchronized<optional<IMPL>> fIndirect_;
181
182 private:
183 // separate this method out so the callers can be inlined, this more rarely executed, and longer segment of code is not
184 static void DoInitOutOfLine_ (typename RWSynchronized<optional<IMPL>>::WritableReference* ref);
185 };
186
187}
188
189/*
190 ********************************************************************************
191 ***************************** Implementation Details ***************************
192 ********************************************************************************
193 */
194#include "ModuleGetterSetter.inl"
195
196#endif /*_Stroika_Foundation_Execution_ModuleGetterSetter_h_*/
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Helper to define synchronized, lazy constructed, module initialization (intended to work with DataExc...
nonvirtual shared_ptr< const T > operator->() const
nonvirtual optional< T > Update(const function< optional< T >(const T &)> &updaterFunction)
Call this with a lambda that will update the associated value (INSIDE a lock (synchronized))