HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
observable.hpp
1// Copyright Take Vos 2020.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "notifier.hpp"
8#include "hires_utc_clock.hpp"
9#include "cast.hpp"
10#include "algorithm.hpp"
11#include "type_traits.hpp"
12#include "concepts.hpp"
13#include <memory>
14#include <functional>
15#include <algorithm>
16#include <type_traits>
17#include <vector>
18#include <atomic>
19
20namespace tt {
21template<typename T>
22class observable;
23
24namespace detail {
25
40template<typename T>
42public:
43 using value_type = T;
44 using notifier_type = notifier<void()>;
45 using callback_type = typename notifier_type::callback_type;
46 using callback_ptr_type = typename notifier_type::callback_ptr_type;
47
48 virtual ~observable_base()
49 {
50 replace_with(_listeners, nullptr);
51 }
52
53 observable_base(observable_base const &) = delete;
55 observable_base &operator=(observable_base const &) = delete;
56 observable_base &operator=(observable_base &&) = delete;
57
60 observable_base(observable<value_type> *owner) noexcept : _owner(owner), _listeners() {}
61
66 [[nodiscard]] virtual value_type load() const noexcept = 0;
67
76 virtual bool store(value_type const &new_value) noexcept = 0;
77
78 void notify() noexcept
79 {
80 _mutex.lock();
81 ttlet listeners = _listeners;
82 ttlet owner = _owner;
83 _mutex.unlock();
84
85 for (ttlet &listener : listeners) {
86 tt_axiom(listener);
87 listener->notify();
88 }
89 tt_axiom(owner);
90 owner->notify();
91 }
92
95 virtual void replace_operand(observable_base *from, observable_base *to) noexcept {}
96
99 void replace_with(observable_base *other) noexcept
100 {
101 _mutex.lock();
102 ttlet listeners = _listeners;
103 _mutex.unlock();
104 return replace_with(std::move(listeners), other);
105 }
106
107 void add_listener(observable_base *listener)
108 {
109 tt_axiom(listener);
110 ttlet lock = std::scoped_lock(_mutex);
111 _listeners.push_back(listener);
112 }
113
114 void remove_listener(observable_base *listener)
115 {
116 tt_axiom(listener);
117 ttlet lock = std::scoped_lock(_mutex);
118 std::erase(_listeners, listener);
119 }
120
121protected:
122 mutable unfair_mutex _mutex;
123 observable<value_type> *_owner;
125
126private:
127 void replace_with(std::vector<observable_base *> listeners, observable_base *other) noexcept
128 {
129 if (other) {
130 other->_owner = std::exchange(_owner, nullptr);
131 }
132 for (auto listener : listeners) {
133 listener->replace_operand(this, other);
134 }
135 }
136};
137
138template<typename T>
139class observable_value final : public observable_base<T> {
140public:
142 using value_type = typename super::value_type;
143
144 static constexpr bool is_atomic = may_be_atomic_v<value_type>;
145 using atomic_type = std::conditional_t<is_atomic, std::atomic<value_type>, value_type>;
146
147 observable_value(observable<value_type> *owner) noexcept : super(owner), _value() {}
148
149 observable_value(observable<value_type> *owner, value_type const &value) noexcept : super(owner), _value(value) {}
150
151 value_type load() const noexcept override
152 {
153 return _load();
154 }
155
156 bool store(value_type const &new_value) noexcept override
157 {
158 ttlet changed = _store(new_value);
159 if (changed) {
160 this->notify();
161 }
162 return changed;
163 }
164
165private:
166 atomic_type _value;
167
168 value_type _load() const noexcept requires(is_atomic)
169 {
170 return _value.load();
171 }
172
173 bool _store(value_type const &new_value) noexcept requires(is_atomic)
174 {
175 if constexpr (std::equality_comparable<T>) {
176 ttlet old_value = _value.exchange(new_value);
177 if (old_value != new_value) {
178 return true;
179 } else {
180 return false;
181 }
182
183 } else {
184 _value.store(new_value);
185 return true;
186 }
187 }
188
189 value_type _load() const noexcept requires(not is_atomic)
190 {
191 ttlet lock = std::scoped_lock(this->_mutex);
192 return _value;
193 }
194
195 bool _store(value_type const &new_value) noexcept requires(not is_atomic)
196 {
197 ttlet lock = std::scoped_lock(this->_mutex);
198 if constexpr (std::equality_comparable<T>) {
199 return std::exchange(_value, new_value) != new_value;
200
201 } else {
202 _value = new_value;
203 return true;
204 }
205 }
206};
207
208template<typename T>
209class observable_chain final : public observable_base<T> {
210public:
212 using base = observable_base<T>;
213 using value_type = typename super::value_type;
214
216 {
217 if (auto operand = _operand.load()) {
218 operand->remove_listener(this);
219 }
220 }
221
222 observable_chain(observable<value_type> *owner, base *operand) noexcept : super(owner), _operand(operand)
223 {
224 if (auto operand_ = _operand.load()) {
225 operand_->add_listener(this);
226 } else {
227 tt_no_default();
228 }
229 }
230
231 virtual value_type load() const noexcept override
232 {
233 if (auto operand = _operand.load()) {
234 return operand->load();
235 } else {
236 tt_no_default();
237 }
238 }
239
240 virtual bool store(value_type const &new_value) noexcept override
241 {
242 if (auto operand = _operand.load()) {
243 return operand->store(new_value);
244 } else {
245 tt_no_default();
246 }
247 }
248
252 void replace_operand(base *from, base *to) noexcept override
253 {
254 tt_axiom(from);
255 if (_operand.compare_exchange_strong(from, to)) {
256 from->remove_listener(this);
257 if (to) {
258 to->add_listener(this);
259 this->notify();
260 }
261 }
262 }
263
264private:
265 std::atomic<base *> _operand;
266};
267
268} // namespace detail
269
279template<typename T>
281public:
282 using value_type = T;
283 using notifier_type = notifier<void()>;
284 using callback_type = typename notifier_type::callback_type;
285 using callback_ptr_type = typename notifier_type::callback_ptr_type;
286
288 {
289 tt_axiom(pimpl);
290 }
291
292 observable(observable &&other) noexcept : pimpl(std::make_unique<detail::observable_value<value_type>>(this))
293 {
294 tt_axiom(&other != this);
295 tt_axiom(other.pimpl);
296 pimpl->replace_with(other.pimpl.get());
297 std::swap(pimpl, other.pimpl);
298 }
299
300 observable &operator=(observable &&other) noexcept
301 {
302 tt_axiom(pimpl);
303 tt_axiom(other.pimpl);
304 pimpl->replace_with(other.pimpl.get());
305 std::swap(pimpl, other.pimpl);
306 // Do not replace the notifier.
307 pimpl->notify();
308 return *this;
309 }
310
311 observable(observable const &other) noexcept :
312 pimpl(std::make_unique<detail::observable_chain<value_type>>(this, other.pimpl.get()))
313 {
314 }
315
316 observable &operator=(observable const &other) noexcept
317 {
318 tt_return_on_self_assignment(other);
319 tt_axiom(other.pimpl);
320 auto new_pimpl = std::make_unique<detail::observable_chain<value_type>>(this, other.pimpl.get());
321 tt_axiom(pimpl);
322 pimpl->replace_with(new_pimpl.get());
323 pimpl = std::move(new_pimpl);
324 pimpl->notify();
325 return *this;
326 }
327
330 observable() noexcept : pimpl(std::make_unique<detail::observable_value<value_type>>(this))
331 {
332 tt_axiom(pimpl);
333 }
334
338 observable(value_type const &value) noexcept : pimpl(std::make_unique<detail::observable_value<value_type>>(this, value))
339 {
340 tt_axiom(pimpl);
341 }
342
345 explicit operator bool() const noexcept
346 {
347 return static_cast<bool>(load());
348 }
349
358 observable &operator=(value_type const &value) noexcept
359 {
360 store(value);
361 return *this;
362 }
363
372 observable &operator+=(value_type const &value) noexcept
373 {
374 store(load() + value);
375 return *this;
376 }
377
386 observable &operator-=(value_type const &value) noexcept
387 {
388 store(load() - value);
389 return *this;
390 }
391
398 [[nodiscard]] value_type load() const noexcept
399 {
400 tt_axiom(pimpl);
401 return pimpl->load();
402 }
403
410 [[nodiscard]] value_type operator*() const noexcept
411 {
412 tt_axiom(pimpl);
413 return pimpl->load();
414 }
415
424 bool store(value_type const &new_value) noexcept
425 {
426 tt_axiom(pimpl);
427 return pimpl->store(new_value);
428 }
429
441 template<typename Callback>
442 requires(std::is_invocable_v<Callback>) [[nodiscard]] callback_ptr_type subscribe(Callback &&callback) noexcept
443 {
444 auto callback_ptr = notifier.subscribe(std::forward<Callback>(callback));
445 (*callback_ptr)();
446 return callback_ptr;
447 }
448
459 callback_ptr_type subscribe(callback_ptr_type const &callback) noexcept
460 {
461 return notifier.subscribe(callback);
462 }
463
468 void unsubscribe(callback_ptr_type const &callback_ptr) noexcept
469 {
470 return notifier.unsubscribe(callback_ptr);
471 }
472
477 [[nodiscard]] value_type operator-() const noexcept
478 {
479 return -*(*this);
480 }
481
488 [[nodiscard]] friend bool operator==(observable const &lhs, observable const &rhs) noexcept
489 {
490 return *lhs == *rhs;
491 }
492
499 [[nodiscard]] friend bool operator==(observable const &lhs, value_type const &rhs) noexcept
500 {
501 return *lhs == rhs;
502 }
503
510 [[nodiscard]] friend bool operator==(value_type const &lhs, observable const &rhs) noexcept
511 {
512 return lhs == *rhs;
513 }
514
521 [[nodiscard]] friend auto operator<=>(observable const &lhs, observable const &rhs) noexcept
522 {
523 return *lhs <=> *rhs;
524 }
525
532 [[nodiscard]] friend auto operator<=>(observable const &lhs, value_type const &rhs) noexcept
533 {
534 return *lhs <=> rhs;
535 }
536
543 [[nodiscard]] friend auto operator<=>(value_type const &lhs, observable const &rhs) noexcept
544 {
545 return lhs <=> *rhs;
546 }
547
554 [[nodiscard]] friend auto operator+(observable const &lhs, observable const &rhs) noexcept
555 {
556 return *lhs + *rhs;
557 }
558
565 [[nodiscard]] friend auto operator+(observable const &lhs, value_type const &rhs) noexcept
566 {
567 return *lhs + rhs;
568 }
569
576 [[nodiscard]] friend auto operator+(value_type const &lhs, observable const &rhs) noexcept
577 {
578 return lhs + *rhs;
579 }
580
587 [[nodiscard]] friend auto operator-(observable const &lhs, observable const &rhs) noexcept
588 {
589 return *lhs - *rhs;
590 }
591
598 [[nodiscard]] friend auto operator-(observable const &lhs, value_type const &rhs) noexcept
599 {
600 return *lhs - rhs;
601 }
602
609 [[nodiscard]] friend auto operator-(value_type const &lhs, observable const &rhs) noexcept
610 {
611 return lhs - *rhs;
612 }
613
614protected:
615 void notify() const noexcept
616 {
617 notifier();
618 }
619
620private:
621 using pimpl_type = detail::observable_base<value_type>;
622
623 notifier_type notifier;
625
626 friend class detail::observable_base<value_type>;
627};
628
633template<typename T>
635 using type = T;
636};
637
638template<typename T>
640 using type = typename observable<T>::value_type;
641};
642
643template<typename T>
644using observable_argument_t = typename observable_argument<T>::type;
645
646} // namespace tt
STL namespace.
An observable value.
Definition observable.hpp:280
observable(value_type const &value) noexcept
Construct a observable holding value.
Definition observable.hpp:338
friend auto operator<=>(observable const &lhs, value_type const &rhs) noexcept
Compare and observable with a value.
Definition observable.hpp:532
observable() noexcept
Default construct a observable holding a default constructed value.
Definition observable.hpp:330
friend auto operator-(observable const &lhs, value_type const &rhs) noexcept
Subtract a value from an observable to a value.
Definition observable.hpp:598
observable & operator=(value_type const &value) noexcept
Assign a new value.
Definition observable.hpp:358
callback_ptr_type subscribe(callback_ptr_type const &callback) noexcept
Subscribe a callback function.
Definition observable.hpp:459
friend auto operator<=>(observable const &lhs, observable const &rhs) noexcept
Compare the value of two observables.
Definition observable.hpp:521
friend auto operator-(observable const &lhs, observable const &rhs) noexcept
Subtract the value of two observables.
Definition observable.hpp:587
friend auto operator+(observable const &lhs, observable const &rhs) noexcept
Add the value of two observables.
Definition observable.hpp:554
observable & operator-=(value_type const &value) noexcept
Inplace subtract a value.
Definition observable.hpp:386
observable & operator+=(value_type const &value) noexcept
Inplace add a value.
Definition observable.hpp:372
friend auto operator+(observable const &lhs, value_type const &rhs) noexcept
Add the value of observables to a value.
Definition observable.hpp:565
friend bool operator==(observable const &lhs, value_type const &rhs) noexcept
Compare and observable with a value.
Definition observable.hpp:499
value_type operator-() const noexcept
Negate and return the value.
Definition observable.hpp:477
friend bool operator==(value_type const &lhs, observable const &rhs) noexcept
Compare and observable with a value.
Definition observable.hpp:510
value_type load() const noexcept
Load the value.
Definition observable.hpp:398
void unsubscribe(callback_ptr_type const &callback_ptr) noexcept
Unsubscribe a callback function.
Definition observable.hpp:468
callback_ptr_type subscribe(Callback &&callback) noexcept
Subscribe a callback function.
Definition observable.hpp:442
value_type operator*() const noexcept
Load the value.
Definition observable.hpp:410
friend bool operator==(observable const &lhs, observable const &rhs) noexcept
Compare the value of two observables.
Definition observable.hpp:488
friend auto operator<=>(value_type const &lhs, observable const &rhs) noexcept
Compare and observable with a value.
Definition observable.hpp:543
bool store(value_type const &new_value) noexcept
Assign a new value.
Definition observable.hpp:424
friend auto operator+(value_type const &lhs, observable const &rhs) noexcept
Add the value of observables to a value.
Definition observable.hpp:576
friend auto operator-(value_type const &lhs, observable const &rhs) noexcept
Subtract an observable value from a value.
Definition observable.hpp:609
Observable abstract base class.
Definition observable.hpp:41
void replace_with(observable_base *other) noexcept
Let other take over the listeners and owner.
Definition observable.hpp:99
virtual void replace_operand(observable_base *from, observable_base *to) noexcept
Replace the operands.
Definition observable.hpp:95
virtual bool store(value_type const &new_value) noexcept=0
Set the value.
observable_base(observable< value_type > *owner) noexcept
Constructor.
Definition observable.hpp:60
virtual value_type load() const noexcept=0
Get the current value.
Definition observable.hpp:139
bool store(value_type const &new_value) noexcept override
Set the value.
Definition observable.hpp:156
value_type load() const noexcept override
Get the current value.
Definition observable.hpp:151
Definition observable.hpp:209
void replace_operand(base *from, base *to) noexcept override
Replace the operand.
Definition observable.hpp:252
virtual value_type load() const noexcept override
Get the current value.
Definition observable.hpp:231
virtual bool store(value_type const &new_value) noexcept override
Set the value.
Definition observable.hpp:240
The value_type of an observable from the constructor argument type.
Definition observable.hpp:634
Definition type_traits.hpp:194
T lock(T... args)
T move(T... args)
T swap(T... args)