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