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