HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
observable.hpp
1// Copyright Take Vos 2021.
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 "unfair_mutex.hpp"
8#include "concepts.hpp"
9#include "notifier.hpp"
10#include <memory>
11#include <vector>
12#include <atomic>
13#include <concepts>
14#include <type_traits>
15#include <mutex>
16
17namespace hi::inline v1 {
18template<typename T>
19class observable;
20
21namespace detail {
22
25template<typename T>
27 using value_type = T;
29
30 struct proxy_type {
31 constexpr proxy_type(proxy_type const&) noexcept = delete;
32 constexpr proxy_type& operator=(proxy_type const&) noexcept = delete;
33
34 constexpr proxy_type(proxy_type&& other) noexcept :
35 actual(std::exchange(other.actual, nullptr)), old_value(std::exchange(other.old_value, {}))
36 {
37 }
38
39 constexpr proxy_type& operator=(proxy_type&& other) noexcept
40 {
41 if (actual) {
42#if HI_BUILD_TYPE == HI_BT_DEBUG
43 // This proxy will not be used anymore, so notifier_owners may cause new proxies to be opened.
44 actual->rw_count = false;
45#endif
46 if (old_value != actual->value) {
47 actual->notifier_owners();
48 }
49 }
50
51 actual = std::exchange(other.actual, nullptr);
52 old_value = std::exchange(other.old_value, {});
53 }
54
55 constexpr proxy_type() noexcept : actual(nullptr), old_value() {}
56
57 constexpr proxy_type(observable_impl *actual) noexcept : actual(actual), old_value(actual->value)
58 {
59#if HI_BUILD_TYPE == HI_BT_DEBUG
60 // Cannot open a read-write proxy when something already has a read proxy open.
61 hi_assert(actual->ro_count == 0);
62 // There may only be one read-write proxy.
63 hi_assert(not std::exchange(actual->rw_count, true));
64#endif
65 }
66
67 constexpr ~proxy_type()
68 {
69 if (actual) {
70#if HI_BUILD_TYPE == HI_BT_DEBUG
71 // This proxy will not be used anymore, so notifier_owners may cause new proxies to be opened.
72 actual->rw_count = false;
73#endif
74 if (old_value != actual->value) {
75 actual->notify_owners();
76 }
77 }
78 }
79
80 constexpr operator value_type &() const noexcept
81 {
82 hi_axiom(actual);
83 return actual->value;
84 }
85
86 constexpr value_type *operator->() const noexcept
87 {
88 hi_axiom(actual);
89 return std::addressof(actual->value);
90 }
91
92 constexpr value_type& operator*() const noexcept
93 {
94 hi_axiom(actual);
95 return actual->value;
96 }
97
98 constexpr value_type *operator&() const noexcept
99 {
100 hi_axiom(actual);
101 return std::addressof(actual->value);
102 }
103
104 observable_impl *actual;
105 value_type old_value;
106 };
107
109 constexpr const_proxy_type(const_proxy_type const& other) noexcept : actual(other.actual)
110 {
111#if HI_BUILD_TYPE == HI_BT_DEBUG
112 if (actual) {
113 hi_assert(actual->rw_count == false);
114 hi_assert(actual->ro_count != 0);
115 ++actual->ro_count;
116 }
117#endif
118 }
119
120 constexpr const_proxy_type(const_proxy_type&& other) noexcept : actual(std::exchange(other.actual, nullptr))
121 {
122#if HI_BUILD_TYPE == HI_BT_DEBUG
123 if (actual) {
124 hi_assert(actual->rw_count == false);
125 hi_assert(actual->ro_count != 0);
126 }
127#endif
128 }
129
130 constexpr const_proxy_type& operator=(const_proxy_type const& other) noexcept
131 {
132#if HI_BUILD_TYPE == HI_BT_DEBUG
133 if (actual) {
134 hi_assert(actual->rw_count == false);
135 hi_assert(actual->ro_count != 0);
136 --actual->ro_count;
137 }
138#endif
139 actual = other.actual;
140#if HI_BUILD_TYPE == HI_BT_DEBUG
141 if (actual) {
142 hi_assert(actual->rw_count == false);
143 hi_assert(actual->ro_count != 0);
144 ++actual->ro_count;
145 }
146#endif
147 }
148
149 constexpr const_proxy_type& operator=(const_proxy_type&& other) noexcept
150 {
151#if HI_BUILD_TYPE == HI_BT_DEBUG
152 if (actual) {
153 hi_assert(actual->rw_count == false);
154 hi_assert(actual->ro_count != 0);
155 --actual->ro_count;
156 }
157#endif
158 actual = std::exchange(other.actual, nullptr);
159 }
160
161 constexpr const_proxy_type(proxy_type&& other) noexcept : actual(std::exchange(other.actual, nullptr))
162 {
163#if HI_BUILD_TYPE == HI_BT_DEBUG
164 if (actual) {
165 hi_assert(actual->rw_count == true);
166 hi_assert(actual->ro_count == 0);
167 actual->rw_count = false;
168 ++actual->ro_count;
169 }
170#endif
171 }
172
173 constexpr const_proxy_type& operator=(proxy_type&& other) noexcept
174 {
175#if HI_BUILD_TYPE == HI_BT_DEBUG
176 if (actual) {
177 hi_assert(actual->rw_count == false);
178 hi_assert(actual->ro_count != 0);
179 --actual->ro_count;
180 }
181#endif
182 actual = std::exchange(other.actual, nullptr);
183#if HI_BUILD_TYPE == HI_BT_DEBUG
184 if (actual) {
185 hi_assert(actual->rw_count == true);
186 hi_assert(actual->ro_count == 0);
187 actual->rw_count = false;
188 ++actual->ro_count;
189 }
190#endif
191 }
192
193 constexpr const_proxy_type() noexcept : actual(nullptr) {}
194
195 constexpr const_proxy_type(observable_impl *actual) noexcept : actual(actual)
196 {
197#if HI_BUILD_TYPE == HI_BT_DEBUG
198 if (actual) {
199 // Cannot open a read-only proxy with a read-write proxy.
200 hi_assert(actual->rw_count == false);
201 ++actual->ro_count;
202 }
203#endif
204 }
205
206 constexpr ~const_proxy_type()
207 {
208#if HI_BUILD_TYPE == HI_BT_DEBUG
209 if (actual) {
210 hi_assert(actual->rw_count == false);
211 hi_assert(actual->ro_count != 0);
212 --actual->ro_count;
213 }
214#endif
215 }
216
217 constexpr operator value_type const &() const noexcept
218 {
219 hi_axiom(actual);
220 return actual->value;
221 }
222
223 constexpr value_type const *operator->() const noexcept
224 {
225 hi_axiom(actual);
226 return std::addressof(actual->value);
227 }
228
229 constexpr value_type const& operator*() const noexcept
230 {
231 hi_axiom(actual);
232 return actual->value;
233 }
234
235 constexpr value_type const *operator&() const noexcept
236 {
237 hi_axiom(actual);
238 return std::addressof(actual->value);
239 }
240
241 observable_impl *actual;
242 };
243
244 value_type value;
246#if HI_BUILD_TYPE == HI_BT_DEBUG
247 size_t ro_count = 0;
248 bool rw_count = false;
249#endif
250
252 {
253 hi_axiom(owners.empty());
254 }
255
256 observable_impl(observable_impl const&) = delete;
258 observable_impl& operator=(observable_impl const&) = delete;
259 observable_impl& operator=(observable_impl&&) = delete;
260
261 observable_impl() noexcept : value() {}
262 observable_impl(std::convertible_to<value_type> auto&& value) noexcept : value(hi_forward(value)) {}
263
264 proxy_type proxy() noexcept
265 {
266 return this;
267 }
268
269 const_proxy_type const_proxy() noexcept
270 {
271 return this;
272 }
273
274 void notify_owners() const noexcept
275 {
276 for (hilet& owner : owners) {
277 owner->_notifier(value);
278 }
279 }
280
285 void add_owner(owner_type& owner) noexcept
286 {
287 hi_axiom(std::find(owners.cbegin(), owners.cend(), &owner) == owners.cend());
288
289 owners.push_back(&owner);
290 }
291
296 void remove_owner(owner_type& owner) noexcept
297 {
298 hilet nr_erased = std::erase(owners, &owner);
299 hi_axiom(nr_erased == 1);
300 }
301
302 void reseat_owners(std::shared_ptr<observable_impl> const& new_impl) noexcept
303 {
304 hi_axiom(not owners.empty());
305
306 auto keep_this_alive = owners.front()->_pimpl;
307
308 for (auto owner : owners) {
309 owner->_pimpl = new_impl;
310 new_impl->owners.push_back(owner);
311 owner->_notifier(value);
312 }
313 owners.clear();
314 }
315};
316
317} // namespace detail
318
355template<typename T>
357public:
358 using value_type = T;
362 using reference = value_type&;
363 using const_reference = value_type const&;
364 using notifier_type = notifier<void(value_type)>;
365 using token_type = notifier_type::token_type;
366 using awaiter_type = notifier_type::awaiter_type;
367
369 {
370 _pimpl->remove_owner(*this);
371 }
372
377 observable() noexcept : _pimpl(std::make_shared<impl_type>())
378 {
379 _pimpl->add_owner(*this);
380 }
381
388 observable(observable const& other) noexcept : _pimpl(other._pimpl)
389 {
390 _pimpl->add_owner(*this);
391 }
392
403 observable& operator=(observable const& other) noexcept
404 {
405 if (this == &other or _pimpl == other._pimpl) {
406 return *this;
407 }
408
409 hi_axiom(_pimpl);
410 _pimpl->reseat_owners(other._pimpl);
411 return *this;
412 }
413
414 token_type subscribe(std::invocable<value_type> auto&& callback) noexcept
415 {
416 return _notifier.subscribe(hi_forward(callback));
417 }
418
419 awaiter_type operator co_await() const noexcept
420 {
421 return _notifier.operator co_await();
422 }
423
433 {
434 hi_axiom(_pimpl);
435 return _pimpl->const_proxy();
436 }
437
446 proxy_type proxy() const noexcept
447 {
448 hi_axiom(_pimpl);
449 return _pimpl->const_proxy();
450 }
451
462 proxy_type proxy() noexcept
463 {
464 hi_axiom(_pimpl);
465 return _pimpl->proxy();
466 }
467
472 const_reference operator*() const noexcept
473 {
474 return *const_proxy();
475 }
476
484 {
485 return const_proxy();
486 }
487
492 observable(std::convertible_to<value_type> auto&& value) noexcept : _pimpl(std::make_shared<impl_type>(hi_forward(value)))
493 {
494 _pimpl->add_owner(*this);
495 }
496
502 observable& operator=(std::convertible_to<value_type> auto&& value) noexcept
503 {
504 hi_axiom(_pimpl);
505 *proxy() = hi_forward(value);
506 return *this;
507 }
508
516 auto operator++() noexcept requires(requires(value_type a) { {++a}; })
517 {
518 return ++*proxy();
519 }
520
528 auto operator--() noexcept requires(requires(value_type a) { {--a}; })
529 {
530 return --*proxy();
531 }
532
540 auto operator++(int) noexcept requires(requires(value_type a) { {a++}; })
541 {
542 return (*proxy())++;
543 }
544
552 auto operator--(int) noexcept requires(requires(value_type a) { {a--}; })
553 {
554 return (*proxy())--;
555 }
556
564 auto operator+=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a += hi_forward(b)}; })
565 {
566 return *proxy() += hi_forward(rhs);
567 }
568
576 auto operator-=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a -= hi_forward(b)}; })
577 {
578 return *proxy() -= hi_forward(rhs);
579 }
580
588 auto operator*=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a *= hi_forward(b)}; })
589 {
590 return *proxy() *= hi_forward(rhs);
591 }
592
600 auto operator/=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a /= hi_forward(b)}; })
601 {
602 return *proxy() /= hi_forward(rhs);
603 }
604
612 auto operator%=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a %= hi_forward(b)}; })
613 {
614 return *proxy() %= hi_forward(rhs);
615 }
616
624 auto operator&=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a &= hi_forward(b)}; })
625 {
626 return *proxy() &= hi_forward(rhs);
627 }
628
636 auto operator|=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a |= hi_forward(b)}; })
637 {
638 return *proxy() |= hi_forward(rhs);
639 }
640
648 auto operator^=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a ^= hi_forward(b)}; })
649 {
650 return *proxy() ^= hi_forward(rhs);
651 }
652
660 auto operator<<=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a <<= hi_forward(b)}; })
661 {
662 return *proxy() <<= hi_forward(rhs);
663 }
664
672 auto operator>>=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a >>= hi_forward(b)}; })
673 {
674 return *proxy() >>= hi_forward(rhs);
675 }
676
677private : std::shared_ptr<impl_type> _pimpl;
678 notifier_type _notifier;
679 friend impl_type;
680};
681
686template<typename T>
688 using type = T;
689};
690
691template<typename T>
693 using type = typename observable<T>::value_type;
694};
695
696template<typename T>
697using observable_argument_t = typename observable_argument<T>::type;
698
699} // namespace hi::inline v1
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition required.hpp:29
STL namespace.
auto operator*=(auto &&rhs) noexcept
Inplace multiply.
Definition observable.hpp:588
auto operator--(int) noexcept
Post decrement.
Definition observable.hpp:552
observable() noexcept
Construct an observable.
Definition observable.hpp:377
observable & operator=(observable const &other) noexcept
Chain with another observable.
Definition observable.hpp:403
auto operator>>=(auto &&rhs) noexcept
Inplace shift right.
Definition observable.hpp:672
auto operator--() noexcept
Pre decrement.
Definition observable.hpp:528
observable(std::convertible_to< value_type > auto &&value) noexcept
Construct an observable with its value set.
Definition observable.hpp:492
auto operator/=(auto &&rhs) noexcept
Inplace divide.
Definition observable.hpp:600
auto operator|=(auto &&rhs) noexcept
Inplace bitwise or.
Definition observable.hpp:636
proxy_type proxy() noexcept
proxy a writable reference to the shared-value.
Definition observable.hpp:462
auto operator++() noexcept
Pre increment.
Definition observable.hpp:516
auto operator&=(auto &&rhs) noexcept
Inplace bitwise and.
Definition observable.hpp:624
const_reference operator*() const noexcept
Dereference to the value.
Definition observable.hpp:472
auto operator%=(auto &&rhs) noexcept
Inplace remainder.
Definition observable.hpp:612
observable(observable const &other) noexcept
Construct an observable and chain it to another.
Definition observable.hpp:388
auto operator+=(auto &&rhs) noexcept
Inplace add.
Definition observable.hpp:564
const_proxy_type operator->() const noexcept
Member select.
Definition observable.hpp:483
auto operator^=(auto &&rhs) noexcept
Inplace bitwise xor.
Definition observable.hpp:648
const_proxy_type const_proxy() const noexcept
proxy a constant reference to the shared value.
Definition observable.hpp:432
auto operator-=(auto &&rhs) noexcept
Inplace subtract.
Definition observable.hpp:576
auto operator<<=(auto &&rhs) noexcept
Inplace shift left.
Definition observable.hpp:660
observable & operator=(std::convertible_to< value_type > auto &&value) noexcept
Assign a new value.
Definition observable.hpp:502
auto operator++(int) noexcept
Post increment.
Definition observable.hpp:540
proxy_type proxy() const noexcept
proxy a constant reference to the shared value.
Definition observable.hpp:446
The shared value, shared between observers.
Definition observable.hpp:26
void remove_owner(owner_type &owner) noexcept
Remove an observer as one of the owners of the shared-value.
Definition observable.hpp:296
void add_owner(owner_type &owner) noexcept
Add an observer as one of the owners of the shared-value.
Definition observable.hpp:285
Definition observable.hpp:30
The value_type of an observable from the constructor argument type.
Definition observable.hpp:687
Definition concepts.hpp:38
T addressof(T... args)
T cbegin(T... args)
T clear(T... args)
T empty(T... args)
T cend(T... args)
T find(T... args)
T front(T... args)
T push_back(T... args)