HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
observer_intf.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 "observed.hpp"
8#include "../utility/utility.hpp"
9#include "../dispatch/dispatch.hpp"
10#include "../concurrency/thread.hpp" // XXX #616
11#include "../macros.hpp"
12#include <concepts>
13#include <utility>
14#include <format>
15#include <new>
16#include <algorithm>
17#include <memory>
18
19hi_export_module(hikogui.observer : observer_intf);
20
21hi_export namespace hi { inline namespace v1 {
22
31template<typename T>
32class observer {
33public:
34 using value_type = T;
35 using notifier_type = notifier<void(value_type)>;
36 using callback_type = notifier_type::callback_type;
37 using awaiter_type = notifier_type::awaiter_type;
38 using path_type = observable_msg::path_type;
39
45 class proxy_type {
46 public:
47 using reference = value_type &;
48 using const_reference = value_type const &;
49 using pointer = value_type *;
50 using const_pointer = value_type const *;
51
57 ~proxy_type() noexcept
58 {
59 if (_observer != nullptr) {
60 hi_axiom_not_null(_ptr);
61 if (_original_value) {
62 if constexpr (requires(value_type const& a, value_type const& b) { a != b; }) {
63 if (*_original_value != *_ptr) {
64 _observer->notify();
65 }
66 } else {
67 _observer->notify();
68 }
69 }
70 }
71 }
72
73 proxy_type(proxy_type const&) = delete;
74 proxy_type& operator=(proxy_type const&) = delete;
75
76 proxy_type(proxy_type&& other) noexcept :
77 _observer(std::exchange(other._observer, nullptr)),
78 _ptr(std::exchange(other._ptr, nullptr)),
79 _original_value(std::exchange(other._original_value, std::nullopt))
80 {
81 }
82
83 proxy_type& operator=(proxy_type&& other) noexcept
84 {
85 _observer = std::exchange(other._observer, nullptr);
86 if(_observer) {
87 _observer->notify();
88 }
89 _ptr = std::exchange(other._ptr, nullptr);
90 _original_value = std::exchange(other._original_value, std::nullopt);
91 return *this;
92 }
93
100 proxy_type(observer *observer, value_type *ptr) noexcept :
101 _observer(observer), _ptr(ptr), _original_value(std::nullopt)
102 {
103 hi_axiom_not_null(_observer);
104 hi_axiom_not_null(_ptr);
105 }
106
113 proxy_type() noexcept :
114 _observer(nullptr), _ptr(nullptr), _original_value(std::nullopt)
115 {
116 }
117
124 reference operator*() noexcept
125 {
126 start_write();
127 return *_ptr;
128 }
129
136 const_reference operator*() const noexcept
137 {
138 hi_axiom_not_null(_ptr);
139 return *_ptr;
140 }
141
149 pointer operator->() noexcept
150 {
151 start_write();
152 return _ptr;
153 }
154
162 const_pointer operator->() const noexcept
163 {
164 hi_axiom_not_null(_ptr);
165 return _ptr;
166 }
167
175 pointer operator&() noexcept
176 {
177 start_write();
178 return _ptr;
179 }
180
188 const_pointer operator&() const noexcept
189 {
190 hi_axiom_not_null(_ptr);
191 return _ptr;
192 }
193
194 // clang-format off
195
196 // prefix operators
197#define X(op) \
198 template<typename Rhs> \
199 decltype(auto) operator op() noexcept \
200 requires requires(value_type& a) { op a; } \
201 { \
202 start_write(); \
203 return op (*_ptr); \
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 requires(value_type& a) { a op; } \
215 { \
216 start_write(); \
217 return (*_ptr) 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 requires(value_type& a, Rhs const& b) { a op b; } \
229 { \
230 start_write(); \
231 return (*_ptr) 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 hi_axiom_not_null(_ptr); \
253 return op (*_ptr); \
254 }
255
256 X(-)
257 X(+)
258 X(~)
259 X(!)
260#undef X
261
262 // binary operators
263#define X(op) \
264 template<typename Rhs> \
265 auto operator op(Rhs const& rhs) const noexcept \
266 requires requires(value_type const& a, Rhs const& b) { a op b; } \
267 { \
268 hi_axiom_not_null(_ptr); \
269 return (*_ptr) op rhs; \
270 }
271
272 X(==)
273 X(<=>)
274 X(+)
275 X(-)
276 X(*)
277 X(/)
278 X(%)
279 X(&)
280 X(|)
281 X(^)
282 X(<<)
283 X(>>)
284#undef X
285
286 // call operator
287 template<typename... Args>
288 auto operator()(Args &&... args) const noexcept
289 requires requires(value_type const & a, Args &&...args) { a(std::forward<Args>(args)...); }
290 {
291 hi_axiom_not_null(_ptr);
292 return (*_ptr)(std::forward<Args>(args)...);
293 }
294
295 template<typename... Args>
296 decltype(auto) operator()(Args &&... args) noexcept
297 requires requires(value_type & a, Args &&...args) { a(std::forward<Args>(args)...); }
298 {
299 start_write();
300 return (*_ptr)(std::forward<Args>(args)...);
301 }
302
303 // index operator
304 // XXX c++23
305 template<typename Arg>
306 auto operator[](Arg && arg) const noexcept
307 requires requires(value_type const & a, Arg &&arg) { a[std::forward<Arg>(arg)]; }
308 {
309 hi_axiom_not_null(_ptr);
310 return (*_ptr)[std::forward<Arg>(arg)];
311 }
312
313 // clang-format on
314
315 private:
316 observer *_observer;
317 value_type *_ptr;
318 std::optional<value_type> _original_value;
319
320 void start_write() noexcept
321 {
322 if (not _original_value) {
323 hi_axiom_not_null(_ptr);
324 _original_value = *_ptr;
325 }
326 }
327
328 friend class observer;
329 };
330
331 using const_reference = value_type const &;
332 using pointer = proxy_type;
333 using const_pointer = value_type const *;
334
335 constexpr ~observer() = default;
336
341 template<forward_of<std::shared_ptr<hi::observed_base>> Observed>
342 observer(Observed&& observed_base) noexcept :
343 observer(group_ptr<hi::observed_base>{std::forward<Observed>(observed_base)}, path_type{}, [](void *base) {
344 return base;
345 })
346 {
347 }
348
353 constexpr explicit observer() noexcept : observer(std::make_shared<observed<value_type>>()) {}
354
357 template<std::convertible_to<value_type> Value>
358 constexpr observer(Value && value) noexcept :
360 {
361 }
362
368 constexpr observer(observer const& other) noexcept : observer(other._observed, other._path, other._convert) {}
369
375 constexpr observer(observer&& other) noexcept :
376 observer(std::move(other._observed), std::move(other._path), std::move(other._convert))
377 {
378 other.reset();
379 }
380
387 constexpr observer& operator=(observer const& other) noexcept
388 {
389 _observed = other._observed;
390 _path = other._path;
391 _convert = other._convert;
392
393 // Rewire the callback subscriptions and notify listeners to this observer.
394 update_state_callback();
395 _observed->notify_group_ptr(observable_msg{_observed->get(), _path});
396 return *this;
397 }
398
406 constexpr observer& operator=(observer&& other) noexcept
407 {
408 _observed = std::move(other._observed);
409 _path = std::move(other._path);
410 _convert = std::move(other._convert);
411 other.reset();
412
413 // Rewire the callback subscriptions and notify listeners to this observer.
414 update_state_callback();
415 _observed->notify_group_ptr(observable_msg{_observed->get(), _path});
416 return *this;
417 }
418
423 void reset() noexcept
424 {
426 _path = {};
427 _convert = [](void *base) {
428 return base;
429 };
430 update_state_callback();
431 }
432
437 [[nodiscard]] const_pointer get() const noexcept
438 {
439 return convert(_observed->get());
440 }
441
446 [[nodiscard]] pointer get() noexcept
447 {
448 return proxy_type{this, convert(_observed->get())};
449 }
450
457 template<forward_of<void(value_type)> Func>
458 [[nodiscard]] callback<void(value_type)> subscribe(Func &&func, callback_flags flags = callback_flags::synchronous) noexcept
459 {
460 return _notifier.subscribe(std::forward<Func>(func), flags);
461 }
462
463 awaiter_type operator co_await() const noexcept
464 {
465 return _notifier.operator co_await();
466 }
467
473 [[nodiscard]] auto sub(auto const& index) const noexcept
474 requires(requires() { std::declval<value_type>()[index]; })
475 {
476 using result_type = std::decay_t<decltype(std::declval<value_type>()[index])>;
477
478 auto new_path = _path;
479 new_path.push_back(std::format("[{}]", index));
481 _observed, std::move(new_path), [convert_copy = this->_convert, index](void *base) -> void * {
482 return std::addressof((*std::launder(static_cast<value_type *>(convert_copy(base))))[index]);
483 }};
484 }
485
492 template<fixed_string Name>
493 [[nodiscard]] auto sub() const noexcept
494 {
495 using result_type = std::decay_t<decltype(selector<value_type>{}.template get<Name>(std::declval<value_type&>()))>;
496
497 auto new_path = _path;
498 new_path.push_back(std::string{Name});
499 // clang-format off
501 _observed,
502 std::move(new_path),
503 [convert_copy = this->_convert](void *base) -> void * {
505 *std::launder(static_cast<value_type *>(convert_copy(base)))));
506 });
507 // clang-format on
508 }
509
510 //
511 // Convenient functions / operators working on temporary proxies.
512 //
513
514 // clang-format off
515
518 template<typename Rhs>
519 observer& operator=(Rhs&& rhs) noexcept
520 requires requires (value_type &a, Rhs &&b) { a = std::forward<Rhs>(b); }
521 {
522 *get() = std::forward<Rhs>(rhs);
523 return *this;
524 }
525
528 const_reference operator*() const noexcept
529 {
530 // This returns a copy of the dereferenced value of the proxy.
531 // The proxy's lifetime will be extended for the copy to be made.
532 return *get();
533 }
534
535 const_pointer operator&() const noexcept
536 {
537 return get();
538 }
539
542 const_pointer operator->() const noexcept
543 {
544 return get();
545 }
546
549 pointer operator->() noexcept
550 {
551 return get();
552 }
553
554 // prefix operators
555#define X(op) \
556 template<typename Rhs> \
557 observer& operator op() noexcept \
558 requires requires(value_type& a) { op a; } \
559 { \
560 op *get(); \
561 return *this; \
562 }
563
564 X(++)
565 X(--)
566#undef X
567
568 // suffix operators
569#define X(op) \
570 template<typename Rhs> \
571 auto operator op(int) noexcept \
572 requires requires(value_type& a) { a op; } \
573 { \
574 return *get() op; \
575 }
576
577 X(++)
578 X(--)
579#undef X
580
581 // inplace operators
582#define X(op) \
583 template<typename Rhs> \
584 observer& operator op(Rhs const& rhs) noexcept \
585 requires requires(value_type& a, Rhs const& b) { a op b; } \
586 { \
587 *get() op rhs; \
588 return *this; \
589 }
590
591 X(+=)
592 X(-=)
593 X(*=)
594 X(/=)
595 X(%=)
596 X(&=)
597 X(|=)
598 X(^=)
599 X(<<=)
600 X(>>=)
601#undef X
602
603 // mono operators
604#define X(op) \
605 template<typename Rhs> \
606 auto operator op() const noexcept \
607 requires requires(value_type const& a) { op a; } \
608 { \
609 return op *get(); \
610 }
611
612 X(-)
613 X(+)
614 X(~)
615 X(!)
616#undef X
617
618 // binary operators
619#define X(op) \
620 template<typename Rhs> \
621 auto operator op(Rhs const& rhs) const noexcept \
622 requires requires(value_type const& a, Rhs const& b) { a op b; } \
623 { \
624 return *get() op rhs; \
625 }
626
627 X(==)
628 X(<=>)
629 X(+)
630 X(-)
631 X(*)
632 X(/)
633 X(%)
634 X(&)
635 X(|)
636 X(^)
637 X(<<)
638 X(>>)
639#undef X
640
641 // call operator
642 template<typename... Args>
643 auto operator()(Args &&... args) const noexcept
644 requires requires(value_type const & a, Args &&...args) { a(std::forward<Args>(args)...); }
645 {
646 return (*get())(std::forward<Args>(args)...);
647 }
648
649 // index operator
650 // XXX c++23
651 template<typename Arg>
652 auto operator[](Arg && arg) const noexcept
653 requires requires(value_type const & a, Arg &&arg) { a[std::forward<Arg>(arg)]; }
654 {
655 return (*get())[std::forward<Arg>(arg)];
656 }
657
658 // clang-format on
659
660private:
661 using observed_type = group_ptr<observed_base>;
662 observed_type _observed = {};
663 path_type _path = {};
664 std::function<void *(void *)> _convert = {};
665 notifier_type _notifier;
666#ifndef NDEBUG
667 value_type _debug_value;
668#endif
669
672 template<forward_of<observed_type> ObservedBase, forward_of<path_type> Path, forward_of<void *(void *)> Converter>
674 ObservedBase&& observed_base,
675 Path&& path,
676 Converter&& converter) noexcept :
677 _observed(std::forward<ObservedBase>(observed_base)), _path(std::forward<Path>(path)), _convert(std::forward<Converter>(converter)), _notifier()
678 {
679 update_state_callback();
680 }
681
682 void notify() const noexcept
683 {
684 _observed->notify_group_ptr(observable_msg{_observed->get(), _path});
685 }
686
687 value_type *convert(void *base) const noexcept
688 {
689 return std::launder(static_cast<value_type *>(_convert(base)));
690 }
691
692 value_type const *convert(void const *base) const noexcept
693 {
694 return std::launder(static_cast<value_type const *>(_convert(const_cast<void *>(base))));
695 }
696
697 void update_state_callback() noexcept
698 {
699 _observed.subscribe([this](observable_msg const& msg) {
700 auto const [msg_it, this_it] = std::mismatch(msg.path.cbegin(), msg.path.cend(), _path.cbegin(), _path.cend());
701 // If the message's path is fully within the this' path, then this is a sub-path.
702 // If this' path is fully within the message's path, then this is along the path.
703 if (msg_it == msg.path.cend() or this_it == _path.cend()) {
704#ifndef NDEBUG
705 _debug_value = *convert(msg.ptr);
706#endif
707 _notifier(*convert(msg.ptr));
708 }
709 });
710
711#ifndef NDEBUG
712 _debug_value = *convert(_observed->get());
713#endif
714 }
715
716 // It is possible to make sub-observables.
717 template<typename>
718 friend class observer;
719};
720
733template<typename T>
735 using type = std::remove_cvref_t<T>;
736};
737
738// clang-format off
739template<std::equality_comparable T> struct observer_decay<observer<T>> { using type = T; };
740template<std::equality_comparable T> struct observer_decay<observer<T> &> { using type = T; };
741template<std::equality_comparable T> struct observer_decay<observer<T> const &> { using type = T; };
742template<std::equality_comparable T> struct observer_decay<observer<T> &&> { using type = T; };
743
744// clang-format on
745
746template<typename T>
747using observer_decay_t = observer_decay<T>::type;
748
749template<typename Context, typename Expected>
750struct is_forward_of<Context, observer<Expected>> :
751 std::conditional_t<std::is_convertible_v<Context, observer<Expected>>, std::true_type, std::false_type> {};
752
753
754template<typename Context, typename Expected>
756
757}} // namespace hi::inline v1
Functions and types for accessing operating system threads.
@ other
The gui_event does not have associated data.
Definition gui_event_variant.hpp:24
STL namespace.
The HikoGUI namespace.
Definition array_generic.hpp:21
The HikoGUI API version 1.
Definition array_generic.hpp:22
Definition callback.hpp:77
Definition observed.hpp:17
std::vector< std::string > path_type
The type of the path used for notifying observers.
Definition observed.hpp:20
An abstract observed object.
Definition observed.hpp:30
Definition observed.hpp:53
A observer pointing to the whole or part of a observed_base.
Definition observer_intf.hpp:32
void reset() noexcept
Reset the observer.
Definition observer_intf.hpp:423
const_pointer get() const noexcept
Read the observed_base value.
Definition observer_intf.hpp:437
observer & operator=(Rhs &&rhs) noexcept
Assign a new value to the observed_base value.
Definition observer_intf.hpp:519
observer(Observed &&observed_base) noexcept
Create an observer from an observed_base.
Definition observer_intf.hpp:342
const_pointer operator->() const noexcept
Constant pointer-to-member of the value being observed_base.
Definition observer_intf.hpp:542
pointer operator->() noexcept
Constant pointer-to-member of the value being observed_base.
Definition observer_intf.hpp:549
constexpr observer(observer &&other) noexcept
Move construct.
Definition observer_intf.hpp:375
constexpr observer & operator=(observer &&other) noexcept
Move assign.
Definition observer_intf.hpp:406
constexpr observer(Value &&value) noexcept
Create a observer linked to an anonymous observed_base-value.
Definition observer_intf.hpp:358
pointer get() noexcept
Make a copy of the observed_base value for modification.
Definition observer_intf.hpp:446
auto sub() const noexcept
Create a sub-observer by selecting a member-variable of the value.
Definition observer_intf.hpp:493
const_reference operator*() const noexcept
Get the value being observed_base.
Definition observer_intf.hpp:528
constexpr observer & operator=(observer const &other) noexcept
Copy assign.
Definition observer_intf.hpp:387
callback< void(value_type)> subscribe(Func &&func, callback_flags flags=callback_flags::synchronous) noexcept
Subscribe a callback to this observer.
Definition observer_intf.hpp:458
constexpr observer(observer const &other) noexcept
Copy construct.
Definition observer_intf.hpp:368
constexpr observer() noexcept
Create a observer linked to an anonymous default initialized observed_base-value.
Definition observer_intf.hpp:353
observer(ObservedBase &&observed_base, Path &&path, Converter &&converter) noexcept
Construct an observer from an observed_base.
Definition observer_intf.hpp:673
auto sub(auto const &index) const noexcept
Create a sub-observer by indexing into the value.
Definition observer_intf.hpp:473
A proxy object of the observer.
Definition observer_intf.hpp:45
reference operator*() noexcept
Dereference the value.
Definition observer_intf.hpp:124
proxy_type() noexcept
Create a proxy object.
Definition observer_intf.hpp:113
pointer operator&() noexcept
Pointer dereference the value.
Definition observer_intf.hpp:175
const_pointer operator&() const noexcept
Pointer dereference the value.
Definition observer_intf.hpp:188
~proxy_type() noexcept
Commits and destruct the proxy object.
Definition observer_intf.hpp:57
pointer operator->() noexcept
Pointer dereference the value.
Definition observer_intf.hpp:149
const_pointer operator->() const noexcept
Pointer dereference the value.
Definition observer_intf.hpp:162
const_reference operator*() const noexcept
Dereference the value.
Definition observer_intf.hpp:136
proxy_type(observer *observer, value_type *ptr) noexcept
Create a proxy object.
Definition observer_intf.hpp:100
A type-trait for observer arguments.
Definition observer_intf.hpp:734
Is context a form of the expected type.
Definition type_traits.hpp:593
This selector allows access to member variable by name.
Definition type_traits.hpp:691
Definition observer_intf.hpp:755
True if T is a forwarded type of Forward.
Definition concepts.hpp:137
T addressof(T... args)
T declval(T... args)
T forward(T... args)
T make_shared(T... args)
T mismatch(T... args)
T move(T... args)
T push_back(T... args)