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