Clio  develop
The XRP Ledger API server.
Loading...
Searching...
No Matches
ObservableValue.hpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of clio: https://github.com/XRPLF/clio
4 Copyright (c) 2025, the clio developers.
5
6 Permission to use, copy, modify, and distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#pragma once
21
22#include <boost/signals2/connection.hpp>
23#include <boost/signals2/signal.hpp>
24#include <boost/signals2/variadic_signal.hpp>
25
26#include <atomic>
27#include <concepts>
28#include <type_traits>
29
30namespace util {
31
32template <typename T>
33concept SomeAtomic =
34 std::same_as<std::remove_cvref_t<T>, std::atomic<std::remove_cvref_t<typename T::value_type>>>;
35
49template <typename T>
50concept Observable =
51 std::equality_comparable<T> && std::copy_constructible<T> && std::move_constructible<T>;
52
53namespace impl {
54
64template <Observable T>
66protected:
67 boost::signals2::signal<void(T const&)> onUpdate_;
68
69public:
70 virtual ~ObservableValueBase() = default;
71
77 boost::signals2::connection
78 observe(std::invocable<T const&> auto&& fn)
79 {
80 return onUpdate_.connect(std::forward<decltype(fn)>(fn));
81 }
82
87 [[nodiscard]] bool
89 {
90 return not onUpdate_.empty();
91 }
92
99 virtual void
101
102protected:
107 void
108 notifyObservers(T const& value)
109 {
110 onUpdate_(value);
111 }
112};
113
114} // namespace impl
115
116// Forward declaration
117template <typename T>
119
142template <Observable T>
143 requires(not SomeAtomic<T>)
144class ObservableValue<T> : public impl::ObservableValueBase<T> {
145 T value_;
146
157 struct ObservableGuard {
158 T const oldValue;
159 ObservableValue<T>& ref;
160
165 ObservableGuard(ObservableValue<T>& observable) : oldValue(observable), ref(observable)
166 {
167 }
168
175 ~ObservableGuard()
176 {
177 if (oldValue != ref.value_)
178 ref.notifyObservers(ref.value_);
179 }
180
185 [[nodiscard]]
186 operator T&()
187 {
188 return ref.value_;
189 }
190 };
191
192public:
197 ObservableValue(std::convertible_to<T> auto&& value)
198 : value_{std::forward<decltype(value)>(value)}
199 {
200 }
201
206 requires std::default_initializable<T>
207 : value_{}
208 {
209 }
210
211 ObservableValue(ObservableValue const&) = delete;
212 ObservableValue(ObservableValue&&) = default;
213 ObservableValue&
214 operator=(ObservableValue const&) = delete;
215 ObservableValue&
216 operator=(ObservableValue&&) = default;
217
229 ObservableValue&
230 operator=(std::convertible_to<T> auto&& val)
231 {
232 set(val);
233 return *this;
234 }
235
244 [[nodiscard]] ObservableGuard
246 {
247 return {*this};
248 }
249
254 [[nodiscard]]
255 operator T const&() const
256 {
257 return value_;
258 }
259
264 [[nodiscard]] T const&
265 get() const
266 {
267 return value_;
268 }
269
285 void
286 set(std::convertible_to<T> auto&& val)
287 {
288 if (value_ != val) {
289 value_ = std::forward<decltype(val)>(val);
290 this->notifyObservers(value_);
291 }
292 }
293
300 void
301 forceNotify() override
302 {
303 this->notifyObservers(value_);
304 }
305};
306
326template <Observable T>
327class ObservableValue<std::atomic<T>> : public impl::ObservableValueBase<T> {
328 std::atomic<T> value_;
329
330public:
335 ObservableValue(std::convertible_to<T> auto&& value)
336 : value_{std::forward<decltype(value)>(value)}
337 {
338 }
339
344 requires std::default_initializable<T>
345 : value_{}
346 {
347 }
348
349 ObservableValue(ObservableValue const&) = delete;
350 ObservableValue(ObservableValue&&) = default;
351 ObservableValue&
352 operator=(ObservableValue const&) = delete;
353 ObservableValue&
354 operator=(ObservableValue&&) = default;
355
365 ObservableValue&
366 operator=(std::convertible_to<T> auto&& val)
367 {
368 set(std::forward<decltype(val)>(val));
369 return *this;
370 }
371
376 [[nodiscard]] T
377 get() const
378 {
379 return value_.load();
380 }
381
386 [[nodiscard]]
387 operator T() const
388 {
389 return get();
390 }
391
400 void
401 set(std::convertible_to<T> auto&& val)
402 {
403 T newValue = std::forward<decltype(val)>(val);
404 T oldValue = value_.load();
405
406 // Use compare-and-swap to atomically update
407 while (!value_.compare_exchange_weak(oldValue, newValue)) {
408 // compare_exchange_weak updates oldValue with current value on failure
409 // Continue until we succeed
410 }
411
412 // Notify observers if we actually changed the value
413 // Note: oldValue now contains the actual previous value that was replaced
414 if (oldValue != newValue) {
415 this->notifyObservers(newValue);
416 }
417 }
418
425 void
426 forceNotify() override
427 {
428 this->notifyObservers(value_.load());
429 }
430};
431
432} // namespace util
ObservableValue & operator=(std::convertible_to< T > auto &&val)
Assignment operator that updates value and notifies observers.
Definition ObservableValue.hpp:230
ObservableGuard operator->()
Provides deferred notification access to the value.
Definition ObservableValue.hpp:245
void forceNotify() override
Forces notification of all observers with the current value.
Definition ObservableValue.hpp:301
ObservableValue()
Constructs ObservableValue with default initial value.
Definition ObservableValue.hpp:205
ObservableValue(std::convertible_to< T > auto &&value)
Constructs ObservableValue with initial value.
Definition ObservableValue.hpp:197
T const & get() const
Explicitly gets the current value.
Definition ObservableValue.hpp:265
void set(std::convertible_to< T > auto &&val)
Sets a new value and notifies observers if changed.
Definition ObservableValue.hpp:286
void set(std::convertible_to< T > auto &&val)
Sets a new atomic value and notifies observers if changed.
Definition ObservableValue.hpp:401
ObservableValue()
Constructs ObservableValue with default initial value.
Definition ObservableValue.hpp:343
T get() const
Gets the current atomic value.
Definition ObservableValue.hpp:377
ObservableValue(std::convertible_to< T > auto &&value)
Constructs ObservableValue with initial atomic value.
Definition ObservableValue.hpp:335
ObservableValue & operator=(std::convertible_to< T > auto &&val)
Assignment operator that updates atomic value and notifies observers.
Definition ObservableValue.hpp:366
void forceNotify() override
Forces notification of all observers with the current value.
Definition ObservableValue.hpp:426
Definition ObservableValue.hpp:118
Base class containing common ObservableValue functionality.
Definition ObservableValue.hpp:65
void notifyObservers(T const &value)
Notifies all observers with the given value.
Definition ObservableValue.hpp:108
boost::signals2::connection observe(std::invocable< T const & > auto &&fn)
Registers an observer callback for value changes.
Definition ObservableValue.hpp:78
bool hasObservers() const
Checks if there are any active observers.
Definition ObservableValue.hpp:88
virtual void forceNotify()=0
Forces notification of all observers with the current value.
Concept defining types that can be observed for changes.
Definition ObservableValue.hpp:50
Definition ObservableValue.hpp:33
This namespace contains various utilities.
Definition AccountUtils.hpp:30