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 = std::same_as<std::remove_cvref_t<T>, std::atomic<std::remove_cvref_t<typename T::value_type>>>;
34
48template <typename T>
49concept Observable = std::equality_comparable<T> && std::copy_constructible<T> && std::move_constructible<T>;
50
51namespace impl {
52
61template <Observable T>
63protected:
64 boost::signals2::signal<void(T const&)> onUpdate_;
65
66public:
67 virtual ~ObservableValueBase() = default;
68
74 boost::signals2::connection
75 observe(std::invocable<T const&> auto&& fn)
76 {
77 return onUpdate_.connect(std::forward<decltype(fn)>(fn));
78 }
79
84 [[nodiscard]] bool
86 {
87 return not onUpdate_.empty();
88 }
89
96 virtual void
98
99protected:
104 void
105 notifyObservers(T const& value)
106 {
107 onUpdate_(value);
108 }
109};
110
111} // namespace impl
112
113// Forward declaration
114template <typename T>
116
138template <Observable T>
139 requires(not SomeAtomic<T>)
140class ObservableValue<T> : public impl::ObservableValueBase<T> {
141 T value_;
142
153 struct ObservableGuard {
154 T const oldValue;
155 ObservableValue<T>& ref;
156
161 ObservableGuard(ObservableValue<T>& observable) : oldValue(observable), ref(observable)
162 {
163 }
164
171 ~ObservableGuard()
172 {
173 if (oldValue != ref.value_)
174 ref.notifyObservers(ref.value_);
175 }
176
181 [[nodiscard]]
182 operator T&()
183 {
184 return ref.value_;
185 }
186 };
187
188public:
193 ObservableValue(std::convertible_to<T> auto&& value) : value_{std::forward<decltype(value)>(value)}
194 {
195 }
196
201 requires std::default_initializable<T>
202 : value_{}
203 {
204 }
205
206 ObservableValue(ObservableValue const&) = delete;
207 ObservableValue(ObservableValue&&) = default;
208 ObservableValue&
209 operator=(ObservableValue const&) = delete;
210 ObservableValue&
211 operator=(ObservableValue&&) = default;
212
224 ObservableValue&
225 operator=(std::convertible_to<T> auto&& val)
226 {
227 set(val);
228 return *this;
229 }
230
239 [[nodiscard]] ObservableGuard
241 {
242 return {*this};
243 }
244
249 [[nodiscard]]
250 operator T const&() const
251 {
252 return value_;
253 }
254
259 [[nodiscard]] T const&
260 get() const
261 {
262 return value_;
263 }
264
280 void
281 set(std::convertible_to<T> auto&& val)
282 {
283 if (value_ != val) {
284 value_ = std::forward<decltype(val)>(val);
285 this->notifyObservers(value_);
286 }
287 }
288
295 void
296 forceNotify() override
297 {
298 this->notifyObservers(value_);
299 }
300};
301
321template <Observable T>
322class ObservableValue<std::atomic<T>> : public impl::ObservableValueBase<T> {
323 std::atomic<T> value_;
324
325public:
330 ObservableValue(std::convertible_to<T> auto&& value) : value_{std::forward<decltype(value)>(value)}
331 {
332 }
333
338 requires std::default_initializable<T>
339 : value_{}
340 {
341 }
342
343 ObservableValue(ObservableValue const&) = delete;
344 ObservableValue(ObservableValue&&) = default;
345 ObservableValue&
346 operator=(ObservableValue const&) = delete;
347 ObservableValue&
348 operator=(ObservableValue&&) = default;
349
359 ObservableValue&
360 operator=(std::convertible_to<T> auto&& val)
361 {
362 set(std::forward<decltype(val)>(val));
363 return *this;
364 }
365
370 [[nodiscard]] T
371 get() const
372 {
373 return value_.load();
374 }
375
380 [[nodiscard]]
381 operator T() const
382 {
383 return get();
384 }
385
394 void
395 set(std::convertible_to<T> auto&& val)
396 {
397 T newValue = std::forward<decltype(val)>(val);
398 T oldValue = value_.load();
399
400 // Use compare-and-swap to atomically update
401 while (!value_.compare_exchange_weak(oldValue, newValue)) {
402 // compare_exchange_weak updates oldValue with current value on failure
403 // Continue until we succeed
404 }
405
406 // Notify observers if we actually changed the value
407 // Note: oldValue now contains the actual previous value that was replaced
408 if (oldValue != newValue) {
409 this->notifyObservers(newValue);
410 }
411 }
412
419 void
420 forceNotify() override
421 {
422 this->notifyObservers(value_.load());
423 }
424};
425
426} // namespace util
ObservableValue & operator=(std::convertible_to< T > auto &&val)
Assignment operator that updates value and notifies observers.
Definition ObservableValue.hpp:225
ObservableGuard operator->()
Provides deferred notification access to the value.
Definition ObservableValue.hpp:240
void forceNotify() override
Forces notification of all observers with the current value.
Definition ObservableValue.hpp:296
ObservableValue()
Constructs ObservableValue with default initial value.
Definition ObservableValue.hpp:200
ObservableValue(std::convertible_to< T > auto &&value)
Constructs ObservableValue with initial value.
Definition ObservableValue.hpp:193
T const & get() const
Explicitly gets the current value.
Definition ObservableValue.hpp:260
void set(std::convertible_to< T > auto &&val)
Sets a new value and notifies observers if changed.
Definition ObservableValue.hpp:281
void set(std::convertible_to< T > auto &&val)
Sets a new atomic value and notifies observers if changed.
Definition ObservableValue.hpp:395
ObservableValue()
Constructs ObservableValue with default initial value.
Definition ObservableValue.hpp:337
T get() const
Gets the current atomic value.
Definition ObservableValue.hpp:371
ObservableValue(std::convertible_to< T > auto &&value)
Constructs ObservableValue with initial atomic value.
Definition ObservableValue.hpp:330
ObservableValue & operator=(std::convertible_to< T > auto &&val)
Assignment operator that updates atomic value and notifies observers.
Definition ObservableValue.hpp:360
void forceNotify() override
Forces notification of all observers with the current value.
Definition ObservableValue.hpp:420
Definition ObservableValue.hpp:115
Base class containing common ObservableValue functionality.
Definition ObservableValue.hpp:62
void notifyObservers(T const &value)
Notifies all observers with the given value.
Definition ObservableValue.hpp:105
boost::signals2::connection observe(std::invocable< T const & > auto &&fn)
Registers an observer callback for value changes.
Definition ObservableValue.hpp:75
bool hasObservers() const
Checks if there are any active observers.
Definition ObservableValue.hpp:85
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:49
Definition ObservableValue.hpp:33
This namespace contains various utilities.
Definition AccountUtils.hpp:30