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