7#include "unfair_mutex.hpp"
22struct observable_impl;
24inline unfair_mutex observable_mutex;
29 uint64_t proxy_count = 0;
38 void start_proxy()
noexcept
40 ttlet lock = std::scoped_lock(observable_mutex);
44 void finish_proxy()
noexcept
46 observable_mutex.lock();
48 tt_axiom(proxy_count != 0);
60 tt_axiom(observable_mutex.is_locked());
72 tt_axiom(observable_mutex.is_locked());
74 if (proxy_count == 0 and not callbacks.
empty()) {
77 observable_mutex.unlock();
79 for (ttlet &callback_ptr : to_call) {
80 if (
auto callback = callback_ptr.lock()) {
85 observable_mutex.unlock();
90inline observable_notifier_type observable_notifier;
101template<
typename T,
bool Constant>
103 using value_type = T;
104 static constexpr bool is_atomic = std::is_scalar_v<T>;
105 static constexpr bool is_constant = Constant;
106 static constexpr bool is_variable = not Constant;
108 enum class state_type : uint8_t { read, write, modified };
111 mutable value_type _original;
112 mutable state_type _state = state_type::none;
118 if (is_variable and _state == state_type::write and _actual->value != _original) {
119 _state = state_type::modified;
123 _actual->
mutex.unlock();
124 observable_notifier.finish_proxy();
127 if (is_variable and _state == state_type::modified) {
128 _actual->notify_owners();
135 observable_notifier.start_proxy();
136 this->_actual->
mutex.lock();
138 tt_axiom(this->_actual);
144 void prepare(state_type new_state)
const noexcept
146 tt_axiom(new_state != state_type::read);
147 if (_state == state_type::read and new_state == state_type::write) {
148 _original = _actual->value;
153 operator value_type()
const noexcept requires(
is_atomic)
155 return _actual->value;
158 operator value_type &()
const noexcept requires(is_variable and not
is_atomic)
160 prepare(state_type::write);
161 return _actual->value;
164 operator value_type
const &()
const noexcept requires(is_constant and not
is_atomic)
166 return _actual->value;
173 tt_return_on_self_assignment(other);
175 return *
this = other._actual->value;
182 tt_return_on_self_assignment(other);
184 return *
this = other._actual->value;
188 template<
typename Arg = value_type>
192 prepare(state_type::write);
193 _actual->value = std::forward<Arg>(arg);
197 template<
typename Arg = value_type>
198 observable_proxy &
operator=(Arg &&arg)
noexcept
199 requires(is_variable and
is_atomic and not std::is_same_v<std::remove_cvref_t<Arg>, observable_proxy>)
203 if (_actual->value.exchange(arg) != arg) {
204 _state = state_type::modified;
209 value_type operator*() const noexcept requires(is_atomic)
211 return _actual->value.load();
214 value_type
const &operator*() const noexcept requires(is_constant and not is_atomic)
216 return _actual->value;
219 value_type &operator*() const noexcept requires(is_variable and not is_atomic)
221 prepare(state_type::write);
222 return _actual->value;
225 value_type
const *operator->() const noexcept requires(is_constant and not is_atomic)
227 return &(_actual->value);
230 value_type *operator->() const noexcept requires(is_variable and not is_atomic)
232 prepare(state_type::write);
233 return &(_actual->value);
236 template<
typename... Args>
237 decltype(
auto)
operator()(Args &&...args)
const noexcept
239 prepare(state_type::write);
240 return _actual->value(std::forward<Args>(args)...);
243 template<
typename Arg>
244 decltype(
auto)
operator[](Arg &&arg)
const noexcept requires(is_variable and not is_atomic)
246 prepare(state_type::write);
247 return _actual->value[std::forward<Arg>(arg)];
250 template<
typename Arg>
251 decltype(
auto)
operator[](Arg &&arg)
const noexcept requires(is_constant and not is_atomic)
253 return const_cast<value_type
const &
>(_actual->value)[std::forward<Arg>(arg)];
257 [[nodiscard]] friend auto operator op(observable_proxy const &lhs, observable_proxy const &rhs) noexcept \
259 return lhs._actual->value op rhs._actual->value; \
262 [[nodiscard]] friend auto operator op(observable_proxy const &lhs, auto const &rhs) noexcept \
264 return lhs._actual->value op rhs; \
267 [[nodiscard]] friend auto operator op(auto const &lhs, observable_proxy const &rhs) noexcept \
269 return lhs op rhs._actual->value; \
284 [[nodiscard]] auto operator op() const noexcept \
286 return op _actual->value; \
294 [[nodiscard]] friend decltype(auto) func(observable_proxy const &rhs) noexcept \
296 return func(rhs._actual->value); \
306 template<
typename Rhs>
307 value_type operator+=(Rhs
const &rhs)
noexcept requires(is_variable and std::is_arithmetic_v<Rhs>)
310 prepare(state_type::modified);
311 return _actual->value += rhs;
313 return _actual->value;
317 template<
typename Rhs>
318 value_type operator+=(Rhs
const &rhs)
noexcept requires(is_variable and not std::is_arithmetic_v<Rhs>)
320 prepare(state_type::write);
321 return _actual->value += rhs;
329 static constexpr bool is_atomic = std::is_scalar_v<T>;
331 using value_type = T;
332 using atomic_value_type = std::conditional_t<is_atomic, std::atomic<value_type>, value_type>;
335 atomic_value_type value;
344 tt_axiom(owners.
empty());
353 observable_impl(value_type
const &value) noexcept : value(value) {}
354 observable_impl(value_type &&value) noexcept : value(
std::move(value)) {}
356 observable_proxy<value_type, false> get() noexcept
358 return observable_proxy<value_type, false>(*
this);
361 observable_proxy<value_type, true> cget() noexcept
363 return observable_proxy<value_type, true>(*
this);
374 ttlet lock = std::scoped_lock(observable_mutex);
384 ttlet lock = std::scoped_lock(observable_mutex);
385 ttlet nr_erased = std::erase(owners, &owner);
386 tt_axiom(nr_erased == 1);
389 void notify_owners() const noexcept
391 observable_mutex.lock();
392 for (
auto owner : owners) {
395 observable_notifier.
notify();
400 observable_mutex.lock();
402 tt_axiom(not owners.
empty());
403 auto keep_this_alive = owners.
front()->_pimpl;
405 for (
auto owner : owners) {
406 owner->_pimpl = new_impl;
407 new_impl->owners.push_back(owner);
411 observable_notifier.
notify();
466 using value_type = T;
471 static constexpr bool is_atomic = impl_type::is_atomic;
475 _pimpl->remove_owner(*
this);
484 _pimpl->add_owner(*
this);
495 _pimpl->add_owner(*
this);
510 tt_return_on_self_assignment(other);
512 _pimpl->reseat_owners(other._pimpl);
526 return _pimpl->cget();
539 return _pimpl->cget();
554 return _pimpl->get();
561 observable(std::convertible_to<value_type>
auto &&value) noexcept :
562 _pimpl(std::make_shared<impl_type>(
std::forward<
decltype(value)>(value)))
564 _pimpl->add_owner(*
this);
572 template<
typename Value = value_type>
576 get() = std::forward<Value>(value);
594 detail::observable_proxy<value_type, false>
operator*() noexcept requires(not is_atomic)
599 detail::observable_proxy<value_type, true> operator->() const noexcept
604 detail::observable_proxy<value_type, false> operator->() noexcept
609 explicit operator bool() const noexcept
611 return static_cast<bool>(
cget());
615 [[nodiscard]] friend auto operator op(observable const &lhs, observable const &rhs) noexcept \
617 return lhs.cget() op rhs.cget(); \
620 [[nodiscard]] friend auto operator op(observable const &lhs, auto const &rhs) noexcept \
622 return lhs.cget() op rhs; \
625 [[nodiscard]] friend auto operator op(auto const &lhs, observable const &rhs) noexcept \
627 return lhs op rhs.cget(); \
635 [[nodiscard]] auto operator op() const noexcept \
645 template<
typename Rhs>
646 value_type operator+=(Rhs
const &rhs)
noexcept
651 callback_ptr_type subscribe(callback_ptr_type
const &callback_ptr)
noexcept
653 ttlet
lock = std::scoped_lock(detail::observable_mutex);
654#if TT_BUILD_TYPE == TT_BT_DEBUG
656 return item.lock() == callback_ptr;
658 tt_axiom(it == _callbacks.
cend());
664 template<
typename Callback>
665 [[nodiscard]] callback_ptr_type subscribe(Callback &&callback)
noexcept requires(std::is_invocable_v<Callback>)
668 subscribe(callback_ptr);
670 detail::observable_mutex.lock();
671 detail::observable_notifier.push(callback_ptr);
672 detail::observable_notifier.notify();
676 void unsubscribe(callback_ptr_type
const &callback_ptr)
noexcept
678 ttlet
lock = std::scoped_lock(detail::observable_mutex);
679 ttlet erase_count = std::erase_if(_callbacks, [&callback_ptr](ttlet &item) {
680 return item.expired() or item.lock() == callback_ptr;
682 tt_axiom(erase_count == 1);
689 void notify() const noexcept
691 auto has_expired_callbacks =
false;
692 for (ttlet &callback_ptr : _callbacks) {
693 if (callback_ptr.expired()) {
694 has_expired_callbacks =
true;
696 detail::observable_notifier.push(callback_ptr);
700 if (has_expired_callbacks) {
701 ttlet erase_count = std::erase_if(_callbacks, [](ttlet &callback_ptr) {
702 return callback_ptr.expired();
704 tt_axiom(erase_count > 0);
722 using type =
typename observable<T>::value_type;
726using observable_argument_t =
typename observable_argument<T>::type;
observable() noexcept
Construct an observable.
Definition observable.hpp:482
observable & operator=(Value &&value) noexcept
Assign a new value.
Definition observable.hpp:573
const_reference cget() const noexcept
Get a constant reference to the shared value.
Definition observable.hpp:524
observable(std::convertible_to< value_type > auto &&value) noexcept
Construct an observable with its value set.
Definition observable.hpp:561
const_reference get() const noexcept
Get a constant reference to the shared value.
Definition observable.hpp:537
observable(observable const &other) noexcept
Construct an observable and chain it to another.
Definition observable.hpp:493
observable & operator=(observable const &other) noexcept
Chain with another observable.
Definition observable.hpp:508
value_type operator*() const noexcept
Get copy of the shared-value.
Definition observable.hpp:584
reference get() noexcept
Get a writable reference to the shared-value.
Definition observable.hpp:552
The shared value, shared between observers.
Definition observable.hpp:328
unfair_mutex mutex
The mutex is used to serialize state to the owners list and non-atomic value.
Definition observable.hpp:340
void remove_owner(owner_type &owner) noexcept
Remove an observer as one of the owners of the shared-value.
Definition observable.hpp:382
void add_owner(owner_type &owner) noexcept
Add an observer as one of the owners of the shared-value.
Definition observable.hpp:370
Definition observable.hpp:26
void push(callback_ptr_type callback_ptr) noexcept
Add a callback to the list.
Definition observable.hpp:58
void notify() noexcept
Notify all callbacks that have been pushed.
Definition observable.hpp:71
A proxy to the shared-value inside an observable.
Definition observable.hpp:102
observable_proxy & operator=(observable_proxy const &other) noexcept
Copy the value from a proxy.
Definition observable.hpp:180
observable_proxy & operator=(observable_proxy &&other) noexcept
Copy the value from a temporary proxy.
Definition observable.hpp:171
The value_type of an observable from the constructor argument type.
Definition observable.hpp:716
Definition type_traits.hpp:315
Definition concepts.hpp:37