HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
observer.hpp
1// Copyright Take Vos 2022.
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 "observable_value.hpp"
8#include "observable.hpp"
9#include "type_traits.hpp"
10#include "concepts.hpp"
11
12namespace hi::inline v1 {
13
22template<typename T>
23class observer {
24public:
25 using value_type = T;
26 using notifier_type = notifier<void(value_type)>;
27 using callback_token = notifier_type::callback_token;
28 using callback_proto = notifier_type::callback_proto;
29 using awaiter_type = notifier_type::awaiter_type;
31
40 template<bool IsWriting>
41 class _proxy {
42 public:
43 constexpr static bool is_writing = IsWriting;
44
45 using void_pointer = std::conditional_t<is_writing, void *, void const *>;
46 using const_void_pointer = void const *;
47
48 using reference = std::conditional_t<is_writing, value_type&, value_type const&>;
49 using const_reference = value_type const&;
50 using pointer = std::conditional_t<is_writing, value_type *, value_type const *>;
51 using const_pointer = value_type const *;
52
58 ~_proxy() noexcept
59 {
60 _commit();
61 }
62
63 _proxy(_proxy const&) = delete;
64 _proxy& operator=(_proxy const&) = delete;
65
66 _proxy(_proxy const& other) noexcept
67 requires(not is_writing)
68 : _observer(other._observer), _base(other._base), _value(other._value)
69 {
70 _observer->read_lock();
71 }
72
73 _proxy& operator=(_proxy const& other) noexcept
74 requires(not is_writing)
75 {
76 _commit();
77 _observer = other._observer;
78 _base = other._base;
79 _value = other._value;
80 _observer->read_lock();
81 return *this;
82 };
83
84 _proxy(_proxy&& other) noexcept :
85 _observer(std::exchange(other._observer, nullptr)),
86 _base(std::exchange(other._base, nullptr)),
87 _value(std::exchange(other._value, nullptr))
88 {
89 }
90
91 _proxy& operator=(_proxy&& other) noexcept
92 {
93 _commit();
94 _observer = std::exchange(other._observer, nullptr);
95 _base = std::exchange(other._base, nullptr);
96 _value = std::exchange(other._value, nullptr);
97 return *this;
98 }
99
102 constexpr _proxy() noexcept = default;
103
110 reference operator*() noexcept
111 {
112 hi_assert_not_null(_value);
113 return *_value;
114 }
115
122 const_reference operator*() const noexcept
123 {
124 hi_assert_not_null(_value);
125 return *_value;
126 }
127
136 {
137 hi_assert_not_null(_value);
138 return _value;
139 }
140
149 {
150 hi_assert_not_null(_value);
151 return _value;
152 }
153
161 const_pointer operator->() const noexcept
162 {
163 hi_assert_not_null(_value);
164 return _value;
165 }
166
173 void commit() noexcept
174 {
175 _commit();
176 _observer = nullptr;
177#ifndef NDEBUG
178 _value = nullptr;
179#endif
180 }
181
188 void abort() noexcept
189 {
190 _abort();
191 _observer = nullptr;
192#ifndef NDEBUG
193 _value = nullptr;
194#endif
195 }
196
197 // clang-format off
198
199 // prefix operators
200#define X(op) \
201 template<typename Rhs> \
202 decltype(auto) operator op() noexcept \
203 requires is_writing and requires(value_type& a) { op a; } \
204 { \
205 return op (*_value); \
206 }
207
208 X(++)
209 X(--)
210#undef X
211
212 // suffix operators
213#define X(op) \
214 template<typename Rhs> \
215 auto operator op(int) noexcept \
216 requires is_writing and requires(value_type& a) { a op; } \
217 { \
218 return (*_value) op; \
219 }
220
221 X(++)
222 X(--)
223#undef X
224
225 // inplace operators
226#define X(op) \
227 template<typename Rhs> \
228 decltype(auto) operator op(Rhs const& rhs) noexcept \
229 requires is_writing and requires(value_type& a, Rhs const& b) { a op b; } \
230 { \
231 return (*_value) op rhs; \
232 }
233
234 X(+=)
235 X(-=)
236 X(*=)
237 X(/=)
238 X(%=)
239 X(&=)
240 X(|=)
241 X(^=)
242 X(<<=)
243 X(>>=)
244#undef X
245
246 // mono operators
247#define X(op) \
248 template<typename Rhs> \
249 auto operator op() const noexcept \
250 requires requires(value_type const& a) { op a; } \
251 { \
252 return op (*_value); \
253 }
254
255 X(-)
256 X(+)
257 X(~)
258 X(!)
259#undef X
260
261 // binary operators
262#define X(op) \
263 template<typename Rhs> \
264 auto operator op(Rhs const& rhs) const noexcept \
265 requires requires(value_type const& a, Rhs const& b) { a op b; } \
266 { \
267 return (*_value) op rhs; \
268 }
269
270 X(==)
271 X(<=>)
272 X(+)
273 X(-)
274 X(*)
275 X(/)
276 X(%)
277 X(&)
278 X(|)
279 X(^)
280 X(<<)
281 X(>>)
282#undef X
283
284 // call operator
285 template<typename... Args>
286 auto operator()(Args &&... args) const noexcept
287 requires requires(value_type const & a, Args &&...args) { a(std::forward<Args>(args)...); }
288 {
289 return (*_value)(std::forward<Args>(args)...);
290 }
291
292 template<typename... Args>
293 decltype(auto) operator()(Args &&... args) noexcept
294 requires is_writing and requires(value_type & a, Args &&...args) { a(std::forward<Args>(args)...); }
295 {
296 return (*_value)(std::forward<Args>(args)...);
297 }
298
299 // index operator
300 // XXX c++23
301 template<typename Arg>
302 auto operator[](Arg && arg) const noexcept
303 requires requires(value_type const & a, Arg &&arg) { a[std::forward<Arg>(arg)]; }
304 {
305 return (*_value)[std::forward<Arg>(arg)];
306 }
307
308 template<typename Arg>
309 decltype(auto) operator[](Arg && arg) noexcept
310 requires is_writing and requires(value_type & a, Arg &&arg) { a[std::forward<Arg>(arg)]; }
311 {
312 return (*_value)[std::forward<Arg>(arg)];
313 }
314
315 // clang-format on
316
317 private:
318 observer const *_observer = nullptr;
319 void_pointer _base = nullptr;
320 pointer _value = nullptr;
321
330 _proxy(observer const *observer, void_pointer base, pointer value) noexcept :
331 _observer(observer), _base(base), _value(value)
332 {
333 hi_assert_not_null(_observer);
334 hi_assert_not_null(_base);
335 hi_assert_not_null(_value);
336 }
337
338 void _commit() noexcept
339 {
340 if (_observer != nullptr) {
341 if constexpr (is_writing) {
342 _observer->commit(_base);
343 } else {
344 _observer->read_unlock();
345 }
346 }
347 }
348
349 void _abort() noexcept
350 {
351 if (_observer != nullptr) {
352 if constexpr (is_writing) {
353 _observer->abort(_base);
354 } else {
355 _observer->read_unlock();
356 }
357 }
358 }
359
360 friend class observer;
361 };
362
363 using proxy = _proxy<true>;
364 using const_proxy = _proxy<false>;
365
366 constexpr ~observer() = default;
367
368 // static_assert(is_forward_of_v<std::shared_ptr<observable>, group_ptr<observable,observable_msg>>);
369
375 observer(group_ptr<observable>{hi_forward(observed)}, path_type{}, [](void *base) {
376 return base;
377 })
378 {
379 }
380
385 constexpr explicit observer() noexcept : observer(std::make_shared<observable_value<value_type>>()) {}
386
389 constexpr observer(std::convertible_to<value_type> auto&& value) noexcept :
391 {
392 }
393
399 constexpr observer(observer const& other) noexcept : observer(other._observed, other._path, other._convert) {}
400
406 constexpr observer(observer&& other) noexcept :
407 observer(std::move(other._observed), std::move(other._path), std::move(other._convert))
408 {
409 other.reset();
410 }
411
418 constexpr observer& operator=(observer const& other) noexcept
419 {
420 _observed = other._observed;
421 _path = other._path;
422 _convert = other._convert;
423
424 // Rewire the callback subscriptions and notify listeners to this observer.
425 update_state_callback();
426 _observed->read_lock();
427 _observed->notify_group_ptr(observable_msg{_observed->read(), _path});
428 _observed->read_unlock();
429 return *this;
430 }
431
439 constexpr observer& operator=(observer&& other) noexcept
440 {
441 _observed = std::move(other._observed);
442 _path = std::move(other._path);
443 _convert = std::move(other._convert);
444 other.reset();
445
446 // Rewire the callback subscriptions and notify listeners to this observer.
447 update_state_callback();
448 _observed->read_lock();
449 _observed->notify_group_ptr({_observed->read(), _path});
450 _observed->read_unlock();
451 return *this;
452 }
453
458 void reset() noexcept
459 {
460 _observed = std::make_shared<observable_value<value_type>>();
461 _path = {};
462 _convert = [](void *base) {
463 return base;
464 };
465 update_state_callback();
466 }
467
472 [[nodiscard]] const_proxy read() const& noexcept
473 {
474 _observed->read_lock();
475 void const *base = _observed->read();
476 return const_proxy{this, base, convert(base)};
477 }
478 const_proxy read() && = delete;
479
484 [[nodiscard]] proxy copy() const& noexcept
485 {
486 _observed->write_lock();
487 void const *old_base = _observed->read();
488 void *new_base = _observed->copy(old_base);
489 return proxy(this, new_base, convert(new_base));
490 }
491
498 [[nodiscard]] callback_token
499 subscribe(forward_of<callback_proto> auto&& function, callback_flags flags = callback_flags::synchronous) noexcept
500 {
501 return _notifier.subscribe(hi_forward(function), flags);
502 }
503
504 awaiter_type operator co_await() const noexcept
505 {
506 return _notifier.operator co_await();
507 }
508
514 [[nodiscard]] auto get(auto const& index) const noexcept
515 requires(requires() { std::declval<value_type>()[index]; })
516 {
517 using result_type = std::decay_t<decltype(std::declval<value_type>()[index])>;
518
519 auto new_path = _path;
520 new_path.push_back(std::format("[{}]", index));
522 _observed, std::move(new_path), [convert_copy = this->_convert, index](void *base) -> void * {
523 return std::addressof((*std::launder(static_cast<value_type *>(convert_copy(base))))[index]);
524 }};
525 }
526
533 template<fixed_string Name>
534 [[nodiscard]] auto get() const noexcept
535 {
536 using result_type = std::decay_t<decltype(selector<value_type>{}.get<Name>(std::declval<value_type&>()))>;
537
538 auto new_path = _path;
539 new_path.push_back(std::string{Name});
540 // clang-format off
542 _observed,
543 std::move(new_path),
544 [convert_copy = this->_convert](void *base) -> void * {
545 return std::addressof(selector<value_type>{}.get<Name>(
546 *std::launder(static_cast<value_type *>(convert_copy(base)))));
547 });
548 // clang-format on
549 }
550
551 //
552 // Convenient functions / operators working on temporary proxies.
553 //
554
555 // clang-format off
556
559 template<typename Rhs>
560 observer& operator=(Rhs&& rhs) noexcept
561 requires requires (value_type &a, Rhs &&b) { a = std::forward<Rhs>(b); }
562 {
563 *copy() = std::forward<Rhs>(rhs);
564 return *this;
565 }
566
569 value_type operator*() const noexcept
570 {
571 // This returns a copy of the dereferenced value of the proxy.
572 // The proxy's lifetime will be extended for the copy to be made.
573 return *read();
574 }
575
578 const_proxy operator->() const& noexcept
579 {
580 return read();
581 }
582 const_proxy operator->() && = delete;
583
584 // prefix operators
585#define X(op) \
586 template<typename Rhs> \
587 observer& operator op() noexcept \
588 requires requires(value_type& a) { op a; } \
589 { \
590 op *copy(); \
591 return *this; \
592 }
593
594 X(++)
595 X(--)
596#undef X
597
598 // suffix operators
599#define X(op) \
600 template<typename Rhs> \
601 auto operator op(int) noexcept \
602 requires requires(value_type& a) { a op; } \
603 { \
604 return *copy() op; \
605 }
606
607 X(++)
608 X(--)
609#undef X
610
611 // inplace operators
612#define X(op) \
613 template<typename Rhs> \
614 observer& operator op(Rhs const& rhs) noexcept \
615 requires requires(value_type& a, Rhs const& b) { a op b; } \
616 { \
617 *copy() op rhs; \
618 return *this; \
619 }
620
621 X(+=)
622 X(-=)
623 X(*=)
624 X(/=)
625 X(%=)
626 X(&=)
627 X(|=)
628 X(^=)
629 X(<<=)
630 X(>>=)
631#undef X
632
633 // mono operators
634#define X(op) \
635 template<typename Rhs> \
636 auto operator op() const noexcept \
637 requires requires(value_type const& a) { op a; } \
638 { \
639 return op *read(); \
640 }
641
642 X(-)
643 X(+)
644 X(~)
645 X(!)
646#undef X
647
648 // binary operators
649#define X(op) \
650 template<typename Rhs> \
651 auto operator op(Rhs const& rhs) const noexcept \
652 requires requires(value_type const& a, Rhs const& b) { a op b; } \
653 { \
654 return *read() op rhs; \
655 }
656
657 X(==)
658 X(<=>)
659 X(+)
660 X(-)
661 X(*)
662 X(/)
663 X(%)
664 X(&)
665 X(|)
666 X(^)
667 X(<<)
668 X(>>)
669#undef X
670
671 // call operator
672 template<typename... Args>
673 auto operator()(Args &&... args) const noexcept
674 requires requires(value_type const & a, Args &&...args) { a(std::forward<Args>(args)...); }
675 {
676 return (*read())(std::forward<Args>(args)...);
677 }
678
679 // index operator
680 // XXX c++23
681 template<typename Arg>
682 auto operator[](Arg && arg) const noexcept
683 requires requires(value_type const & a, Arg &&arg) { a[std::forward<Arg>(arg)]; }
684 {
685 return (*read())[std::forward<Arg>(arg)];
686 }
687
688 // clang-format on
689
690private:
691 using observed_type = group_ptr<observable>;
692 observed_type _observed = {};
693 path_type _path = {};
694 std::function<void *(void *)> _convert = {};
695 notifier_type _notifier;
696#ifndef NDEBUG
697 value_type _debug_value;
698#endif
699
703 forward_of<observed_type> auto&& observed,
704 forward_of<path_type> auto&& path,
705 forward_of<void *(void *)> auto&& converter) noexcept :
706 _observed(hi_forward(observed)), _path(hi_forward(path)), _convert(hi_forward(converter)), _notifier()
707 {
708 update_state_callback();
709 }
710
711 void read_unlock() const noexcept
712 {
713 _observed->read_unlock();
714 }
715
716 void commit(void *base) const noexcept
717 {
718 if constexpr (requires(value_type const& a, value_type const& b) { a == b; }) {
719 // Only commit and notify when the value has actually changed.
720 // Since there is a write-lock being held, _observed->read() will be the previous value.
721 if (*convert(_observed->read()) != *convert(base)) {
722 _observed->commit(base);
723 _observed->notify_group_ptr({base, _path});
724 } else {
725 _observed->abort(base);
726 }
727 } else {
728 _observed->commit(base);
729 _observed->notify_group_ptr({base, _path});
730 }
731 _observed->write_unlock();
732 }
733
734 void abort(void *base) const noexcept
735 {
736 _observed->abort(base);
737 _observed->write_unlock();
738 }
739
740 value_type *convert(void *base) const noexcept
741 {
742 return std::launder(static_cast<value_type *>(_convert(base)));
743 }
744
745 value_type const *convert(void const *base) const noexcept
746 {
747 return std::launder(static_cast<value_type const *>(_convert(const_cast<void *>(base))));
748 }
749
750 void update_state_callback() noexcept
751 {
752 _observed.subscribe([this](observable_msg const& msg) {
753 auto [msg_it, this_it] = std::mismatch(msg.path.cbegin(), msg.path.cend(), _path.cbegin(), _path.cend());
754 // If the message's path is fully within the this' path, then this is a sub-path.
755 // If this' path is fully within the message's path, then this is along the path.
756 if (msg_it == msg.path.cend() or this_it == _path.cend()) {
757#ifndef NDEBUG
758 _notifier(_debug_value = *convert(msg.ptr));
759#else
760 _notifier(*convert(msg.ptr));
761#endif
762 }
763 });
764
765#ifndef NDEBUG
766 _observed->read_lock();
767 _debug_value = *convert(_observed->read());
768 _observed->read_unlock();
769#endif
770 }
771
772 // It is possible to make sub-observables.
773 template<typename>
774 friend class observer;
775};
776
789template<typename T>
791 using type = std::decay_t<T>;
792};
793
794// clang-format off
795template<typename T> struct observer_decay<observer<T>> { using type = T; };
796template<typename T> struct observer_decay<observer<T> &> { using type = T; };
797template<typename T> struct observer_decay<observer<T> const &> { using type = T; };
798template<typename T> struct observer_decay<observer<T> &&> { using type = T; };
799
800// clang-format on
801
802template<typename T>
803using observer_decay_t = observer_decay<T>::type;
804
805template<typename Context, typename Expected>
806struct is_forward_of<Context, observer<Expected>> :
807 std::conditional_t<std::is_convertible_v<Context, observer<Expected>>, std::true_type, std::false_type> {};
808
809} // namespace hi::inline v1
#define hi_assert_not_null(x,...)
Assert if an expression is not nullptr.
Definition assert.hpp:118
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
@ read
Allow read access to a file.
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:15
callback_flags
Definition callback_flags.hpp:12
Definition functional.hpp:14
A smart pointer which manages ownership as a group.
Definition group_ptr.hpp:146
Definition observable.hpp:15
A observer pointing to the whole or part of a observable.
Definition observer.hpp:23
observer(forward_of< std::shared_ptr< observable > > auto &&observed) noexcept
Create an observer from an observable.
Definition observer.hpp:374
value_type operator*() const noexcept
Get a copy of the value being observed.
Definition observer.hpp:569
proxy copy() const &noexcept
Make a copy of the observed value for modification.
Definition observer.hpp:484
void reset() noexcept
Reset the observer.
Definition observer.hpp:458
observer(forward_of< observed_type > auto &&observed, forward_of< path_type > auto &&path, forward_of< void *(void *)> auto &&converter) noexcept
Construct an observer from an observable.
Definition observer.hpp:702
auto get(auto const &index) const noexcept
Create a sub-observer by indexing into the value.
Definition observer.hpp:514
auto get() const noexcept
Create a sub-observer by selecting a member-variable of the value.
Definition observer.hpp:534
const_proxy operator->() const &noexcept
Constant pointer-to-member of the value being observed.
Definition observer.hpp:578
constexpr observer & operator=(observer const &other) noexcept
Copy assign.
Definition observer.hpp:418
constexpr observer(observer &&other) noexcept
Move construct.
Definition observer.hpp:406
constexpr observer(observer const &other) noexcept
Copy construct.
Definition observer.hpp:399
observer & operator=(Rhs &&rhs) noexcept
Assign a new value to the observed value.
Definition observer.hpp:560
constexpr observer() noexcept
Create a observer linked to an anonymous default initialized observed-value.
Definition observer.hpp:385
const_proxy read() const &noexcept
Read the observed value.
Definition observer.hpp:472
callback_token subscribe(forward_of< callback_proto > auto &&function, callback_flags flags=callback_flags::synchronous) noexcept
Subscribe a callback to this observer.
Definition observer.hpp:499
constexpr observer & operator=(observer &&other) noexcept
Move assign.
Definition observer.hpp:439
constexpr observer(std::convertible_to< value_type > auto &&value) noexcept
Create a observer linked to an anonymous observed-value.
Definition observer.hpp:389
Definition observable_value.hpp:16
A proxy object of the observer.
Definition observer.hpp:41
~_proxy() noexcept
Commits and destruct the proxy object.
Definition observer.hpp:58
pointer operator->() noexcept
Pointer dereference the value.
Definition observer.hpp:135
const_reference operator*() const noexcept
Dereference the value.
Definition observer.hpp:122
const_pointer operator->() const noexcept
Pointer dereference the value.
Definition observer.hpp:161
void abort() noexcept
Revert any changes to the value.
Definition observer.hpp:188
_proxy(observer const *observer, void_pointer base, pointer value) noexcept
Create a proxy object.
Definition observer.hpp:330
pointer operator&() noexcept
Pointer dereference the value.
Definition observer.hpp:148
void commit() noexcept
Commit the changes to the value early.
Definition observer.hpp:173
constexpr _proxy() noexcept=default
Construct an empty proxy object.
A type-trait for observer arguments.
Definition observer.hpp:790
Is context a form of the expected type.
Definition type_traits.hpp:481
This selector allows access to member variable by name.
Definition type_traits.hpp:592
Definition concepts.hpp:36
Definition concepts.hpp:39
True if T is a forwarded type of Forward.
Definition concepts.hpp:130
T abort(T... args)
T addressof(T... args)
T make_shared(T... args)
T mismatch(T... args)
T move(T... args)