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 hilet values_are_same = keep_this_alive->value == new_impl->value;
308
309 for (auto owner : owners) {
310 owner->_pimpl = new_impl;
311 new_impl->owners.push_back(owner);
312 if (not values_are_same) {
313 owner->_notifier(value);
314 }
315 }
316 owners.clear();
317 }
318};
319
320} // namespace detail
321
358template<typename T>
360public:
361 using value_type = T;
365 using reference = value_type&;
366 using const_reference = value_type const&;
367 using notifier_type = notifier<void(value_type)>;
368 using token_type = notifier_type::token_type;
369 using awaiter_type = notifier_type::awaiter_type;
370
372 {
373 _pimpl->remove_owner(*this);
374 }
375
380 observable() noexcept : _pimpl(std::make_shared<impl_type>())
381 {
382 _pimpl->add_owner(*this);
383 }
384
391 observable(observable const& other) noexcept : _pimpl(other._pimpl)
392 {
393 _pimpl->add_owner(*this);
394 }
395
406 observable& operator=(observable const& other) noexcept
407 {
408 if (this == &other or _pimpl == other._pimpl) {
409 return *this;
410 }
411
412 hi_axiom(_pimpl);
413 _pimpl->reseat_owners(other._pimpl);
414 return *this;
415 }
416
417 token_type subscribe(callback_flags flags, std::invocable<value_type> auto&& callback) noexcept
418 {
419 return _notifier.subscribe(flags, hi_forward(callback));
420 }
421
422 token_type subscribe(std::invocable<value_type> auto&& callback) noexcept
423 {
424 return _notifier.subscribe(callback_flags::synchronous, hi_forward(callback));
425 }
426
427 awaiter_type operator co_await() const noexcept
428 {
429 return _notifier.operator co_await();
430 }
431
441 {
442 hi_axiom(_pimpl);
443 return _pimpl->const_proxy();
444 }
445
454 proxy_type proxy() const noexcept
455 {
456 hi_axiom(_pimpl);
457 return _pimpl->const_proxy();
458 }
459
470 proxy_type proxy() noexcept
471 {
472 hi_axiom(_pimpl);
473 return _pimpl->proxy();
474 }
475
480 const_reference operator*() const noexcept
481 {
482 return *const_proxy();
483 }
484
492 {
493 return const_proxy();
494 }
495
500 observable(std::convertible_to<value_type> auto&& value) noexcept : _pimpl(std::make_shared<impl_type>(hi_forward(value)))
501 {
502 _pimpl->add_owner(*this);
503 }
504
510 observable& operator=(std::convertible_to<value_type> auto&& value) noexcept
511 {
512 hi_axiom(_pimpl);
513 *proxy() = hi_forward(value);
514 return *this;
515 }
516
524 auto operator++() noexcept requires(requires(value_type a) { {++a}; })
525 {
526 return ++*proxy();
527 }
528
536 auto operator--() noexcept requires(requires(value_type a) { {--a}; })
537 {
538 return --*proxy();
539 }
540
548 auto operator++(int) noexcept requires(requires(value_type a) { {a++}; })
549 {
550 return (*proxy())++;
551 }
552
560 auto operator--(int) noexcept requires(requires(value_type a) { {a--}; })
561 {
562 return (*proxy())--;
563 }
564
572 auto operator+=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a += hi_forward(b)}; })
573 {
574 return *proxy() += hi_forward(rhs);
575 }
576
584 auto operator-=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a -= hi_forward(b)}; })
585 {
586 return *proxy() -= hi_forward(rhs);
587 }
588
596 auto operator*=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a *= hi_forward(b)}; })
597 {
598 return *proxy() *= hi_forward(rhs);
599 }
600
608 auto operator/=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a /= hi_forward(b)}; })
609 {
610 return *proxy() /= hi_forward(rhs);
611 }
612
620 auto operator%=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a %= hi_forward(b)}; })
621 {
622 return *proxy() %= hi_forward(rhs);
623 }
624
632 auto operator&=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a &= hi_forward(b)}; })
633 {
634 return *proxy() &= hi_forward(rhs);
635 }
636
644 auto operator|=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a |= hi_forward(b)}; })
645 {
646 return *proxy() |= hi_forward(rhs);
647 }
648
656 auto operator^=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a ^= hi_forward(b)}; })
657 {
658 return *proxy() ^= hi_forward(rhs);
659 }
660
668 auto operator<<=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a <<= hi_forward(b)}; })
669 {
670 return *proxy() <<= hi_forward(rhs);
671 }
672
680 auto operator>>=(auto&& rhs) noexcept requires(requires(value_type a, decltype(rhs) b) { {a >>= hi_forward(b)}; })
681 {
682 return *proxy() >>= hi_forward(rhs);
683 }
684
685private : std::shared_ptr<impl_type> _pimpl;
686 notifier_type _notifier;
687 friend impl_type;
688};
689
694template<typename T>
696 using type = T;
697};
698
699template<typename T>
701 using type = typename observable<T>::value_type;
702};
703
704template<typename T>
705using observable_argument_t = typename observable_argument<T>::type;
706
707} // 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:596
auto operator--(int) noexcept
Post decrement.
Definition observable.hpp:560
observable() noexcept
Construct an observable.
Definition observable.hpp:380
observable & operator=(observable const &other) noexcept
Chain with another observable.
Definition observable.hpp:406
auto operator>>=(auto &&rhs) noexcept
Inplace shift right.
Definition observable.hpp:680
auto operator--() noexcept
Pre decrement.
Definition observable.hpp:536
observable(std::convertible_to< value_type > auto &&value) noexcept
Construct an observable with its value set.
Definition observable.hpp:500
auto operator/=(auto &&rhs) noexcept
Inplace divide.
Definition observable.hpp:608
auto operator|=(auto &&rhs) noexcept
Inplace bitwise or.
Definition observable.hpp:644
proxy_type proxy() noexcept
proxy a writable reference to the shared-value.
Definition observable.hpp:470
auto operator++() noexcept
Pre increment.
Definition observable.hpp:524
auto operator&=(auto &&rhs) noexcept
Inplace bitwise and.
Definition observable.hpp:632
const_reference operator*() const noexcept
Dereference to the value.
Definition observable.hpp:480
auto operator%=(auto &&rhs) noexcept
Inplace remainder.
Definition observable.hpp:620
observable(observable const &other) noexcept
Construct an observable and chain it to another.
Definition observable.hpp:391
auto operator+=(auto &&rhs) noexcept
Inplace add.
Definition observable.hpp:572
const_proxy_type operator->() const noexcept
Member select.
Definition observable.hpp:491
auto operator^=(auto &&rhs) noexcept
Inplace bitwise xor.
Definition observable.hpp:656
const_proxy_type const_proxy() const noexcept
proxy a constant reference to the shared value.
Definition observable.hpp:440
auto operator-=(auto &&rhs) noexcept
Inplace subtract.
Definition observable.hpp:584
auto operator<<=(auto &&rhs) noexcept
Inplace shift left.
Definition observable.hpp:668
observable & operator=(std::convertible_to< value_type > auto &&value) noexcept
Assign a new value.
Definition observable.hpp:510
auto operator++(int) noexcept
Post increment.
Definition observable.hpp:548
proxy_type proxy() const noexcept
proxy a constant reference to the shared value.
Definition observable.hpp:454
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:695
Definition concepts.hpp:39
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)