diff --git a/.clang-tidy b/.clang-tidy index b4aa20f2..1df1ea1b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -32,4 +32,50 @@ Checks: '-*, -hicpp-vararg, -hicpp-member-init, misc-*, - -misc-non-private-member-variables-in-classes, + -misc-non-private-member-variables-in-classes' + +# Disabled Options Explanation: +# CppCoreGuidelines: +# cppcoreguidelines-pro-bounds-constant-array-index: requires use of guideline support library +# cppcoreguidelines-pro-type-vararg: Mostly triggers for functions we didn't write in acceptable +# situations. +# cppcoreguidelines-pro-type-member-init: Even though this rule is useful, it cannot be uphold in +# some cases with generated constructors and would break a lot of code. +# cppcoreguidelines-non-private-member-variables-in-classes: This rule cannot be followed because +# of testing requirements to make some members protected. + +# Llvm: +# llvm-header-guard: pragma once allowed +# llvm-include-order: Clang format sorts the includes, should maybe fixed in clang-format file. + +# Modernize: +# modernize-use-nodiscard: wants nodiscard for every function. +# modernize-avoid-c-arrays: duplicate rule. +# modernize-use-using: Cannot apply to SDK. + +# Performance: +# performance-noexcept-move-constructor: This is cannot be generally applied and is +# potentially dangerous if applied wrongly. +# Performance improvement from this is questionable and less relevant. + +# Readability: +# readability-redundant-member-init: Initializing all members of a class even if unnecessary is +# not bad style +# readability-uppercase-literal-suffix +# readability-else-after-return: Rule cannot be followed +# readability-named-parameter: Breaks builds with unused variables +# readability-redundant-access-specifiers: Redundant access specifiers (public, private) can +# actually increase readability by introducing visual barriers +# readability-magic-numbers: cppcoreguidelines has a rule for it, no duplicate needed. + +# Hicpp: +# hicpp-uppercase-literal-suffix +# hicpp-noexcept-move: This cannot be generally applied and is potentially dangerous if applied +# wrongly. Performance improvement from this is questionable and less relevant. +# hicpp-vararg: Mostly triggers for functions we didn't write in acceptable situations. +# hicpp-member-init: Even though this rule is useful, it cannot be uphold in some cases with +# generated constructors and would break a lot of code. +# hicpp-avoid-c-arrays: duplicate rule. + +# Misc: +# misc-non-private-member-variables-in-classes: Public members should be allowed in structs diff --git a/nui/include/nui/event_system/event.hpp b/nui/include/nui/event_system/event.hpp new file mode 100644 index 00000000..5ab1b8a3 --- /dev/null +++ b/nui/include/nui/event_system/event.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +namespace Nui +{ + struct EventImpl + { + virtual bool call(std::size_t eventId) = 0; + virtual bool valid() const = 0; + virtual ~EventImpl() = default; + }; + + struct TwoFunctorEventImpl : public EventImpl + { + TwoFunctorEventImpl(std::function action, std::function valid) + : action_{std::move(action)} + , valid_{std::move(valid)} + {} + + bool call(std::size_t eventId) override + { + auto result = action_(eventId); + return result; + } + + bool valid() const override + { + return valid_(); + } + + private: + std::function action_; + std::function valid_; + }; + + class Event + { + public: + Event( + std::function action, + std::function valid = + [] { + return true; + }) + : impl_{std::make_unique(std::move(action), std::move(valid))} + {} + Event(Event const&) = delete; + Event(Event&&) = default; + Event& operator=(Event const&) = delete; + Event& operator=(Event&&) = default; + + operator bool() const + { + return impl_->valid(); + } + bool operator()(std::size_t eventId) const + { + return impl_->call(eventId); + } + + private: + std::unique_ptr impl_; + }; +} \ No newline at end of file diff --git a/nui/include/nui/event_system/event_context.hpp b/nui/include/nui/event_system/event_context.hpp new file mode 100644 index 00000000..8275c0d7 --- /dev/null +++ b/nui/include/nui/event_system/event_context.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include + +namespace Nui +{ + class EventEngine + { + public: + EventEngine() = default; + EventEngine(const EventEngine&) = default; + EventEngine(EventEngine&&) = default; + EventEngine& operator=(const EventEngine&) = default; + EventEngine& operator=(EventEngine&&) = default; + virtual ~EventEngine() = default; + + virtual EventRegistry& eventRegistry() = 0; + }; + + class DefaultEventEngine : public EventEngine + { + public: + EventRegistry& eventRegistry() override + { + return eventRegistry_; + } + + private: + EventRegistry eventRegistry_; + }; + + /** + * @brief This object can be copied with low cost. + */ + class EventContext + { + public: + using EventIdType = EventRegistry::EventIdType; + + EventContext() + : impl_{std::make_shared()} + {} + EventContext(EventContext const&) = default; + EventContext(EventContext&&) = default; + EventContext& operator=(EventContext const&) = default; + EventContext& operator=(EventContext&&) = default; + ~EventContext() = default; + + EventIdType registerEvent(Event event) + { + return impl_->eventRegistry().registerEvent(std::move(event)); + } + auto activateEvent(EventIdType id) + { + return impl_->eventRegistry().activateEvent(id); + } + void executeActiveEventsImmediately() + { + impl_->eventRegistry().executeActiveEvents(); + } + void executeEvent(EventIdType id) + { + impl_->eventRegistry().executeEvent(id); + } + EventIdType registerAfterEffect(Event event) + { + return impl_->eventRegistry().registerAfterEffect(std::move(event)); + } + void cleanInvalidEvents() + { + impl_->eventRegistry().cleanInvalidEvents(); + } + void reset() + { + impl_->eventRegistry().clear(); + } + + private: + std::shared_ptr impl_; + }; + + extern thread_local EventContext globalEventContext; +} \ No newline at end of file diff --git a/nui/include/nui/event_system/event_registry.hpp b/nui/include/nui/event_system/event_registry.hpp new file mode 100644 index 00000000..45430fcd --- /dev/null +++ b/nui/include/nui/event_system/event_registry.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace Nui +{ + class EventRegistry + { + public: + using RegistryType = SelectablesRegistry; + using EventIdType = SelectablesRegistry::IdType; + constexpr static EventIdType invalidEventId = std::numeric_limits::max(); + + public: + EventRegistry() = default; + EventRegistry(const EventRegistry&) = delete; + EventRegistry(EventRegistry&&) = default; + EventRegistry& operator=(const EventRegistry&) = delete; + EventRegistry& operator=(EventRegistry&&) = default; + ~EventRegistry() = default; + + EventIdType registerEvent(Event event) + { + return registry_.append(std::move(event)); + } + + /** + * @brief Returns a pointer to the selected event (only valid until the next activation or event execution). May + * return nullptr when the event id was not found. + * + * @param id + * @return auto* + */ + RegistryType::SelectionResult activateEvent(EventIdType id) + { + return registry_.select(id); + } + + EventIdType registerAfterEffect(Event event) + { + return afterEffects_.append(std::move(event)); + } + + void executeEvent(EventIdType id) + { + registry_.deselect(id, [](SelectablesRegistry::ItemWithId const& itemWithId) -> bool { + if (!itemWithId.item) + return false; + return itemWithId.item.value()(itemWithId.id); + }); + } + + void executeActiveEvents() + { + registry_.deselectAll([](SelectablesRegistry::ItemWithId const& itemWithId) -> bool { + if (!itemWithId.item) + return false; + return itemWithId.item.value()(itemWithId.id); + }); + afterEffects_.deselectAll([](SelectablesRegistry::ItemWithId const& itemWithId) -> bool { + if (!itemWithId.item) + return false; + return itemWithId.item.value()(itemWithId.id); + }); + } + + void cleanInvalidEvents() + { + std::vector invalidIds; + for (auto const& itemWithId : registry_.rawRange()) + { + if (!itemWithId.item) + continue; + if (!static_cast(itemWithId.item.value())) + invalidIds.push_back(itemWithId.id); + } + + for (auto const& id : invalidIds) + registry_.erase(id); + } + + void clear() + { + registry_.clear(); + afterEffects_.clear(); + } + + private: + RegistryType registry_; + RegistryType afterEffects_; + }; +} \ No newline at end of file diff --git a/nui/include/nui/event_system/listen.hpp b/nui/include/nui/event_system/listen.hpp new file mode 100644 index 00000000..c2f5e82f --- /dev/null +++ b/nui/include/nui/event_system/listen.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#include +#include +#include + +namespace Nui +{ + template + void listen(EventContext& eventContext, Observed const& obs, std::function onEvent) + { + const auto eventId = eventContext.registerEvent(Event{ + [obs = Detail::CopyableObservedWrap{obs}, onEvent = std::move(onEvent)](auto) { + return onEvent(obs.value()); + }, + []() { + return true; + }}); + obs.attachEvent(eventId); + } + + template + void listen(EventContext& eventContext, Observed const& obs, std::function onEvent) + { + return listen(eventContext, obs, [onEvent = std::move(onEvent)](ValueT const& value) { + onEvent(value); + return true; + }); + } + + template + void listen(EventContext& eventContext, Observed const& obs, FunctionT onEvent) + { + return listen(eventContext, obs, std::function(std::move(onEvent))); + } + + template + void listen( + EventContext& eventContext, + std::shared_ptr> const& obs, + std::function onEvent) + { + const auto eventId = eventContext.registerEvent(Event{ + [weak = std::weak_ptr>{obs}, onEvent = std::move(onEvent)](auto) { + if (auto obs = weak.lock(); obs) + return onEvent(obs->value()); + return false; + }, + [weak = std::weak_ptr>{obs}]() { + return !weak.expired(); + }}); + obs->attachEvent(eventId); + } + + template + void listen( + EventContext& eventContext, + std::shared_ptr> const& obs, + std::function onEvent) + { + return listen(eventContext, obs, [onEvent = std::move(onEvent)](ValueT const& value) { + onEvent(value); + return true; + }); + } + + template + void listen(EventContext& eventContext, std::shared_ptr> const& obs, FunctionT onEvent) + { + return listen(eventContext, obs, std::function(std::move(onEvent))); + } +} \ No newline at end of file diff --git a/nui/include/nui/event_system/observed_value.hpp b/nui/include/nui/event_system/observed_value.hpp new file mode 100644 index 00000000..c9cae00f --- /dev/null +++ b/nui/include/nui/event_system/observed_value.hpp @@ -0,0 +1,1308 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Nui +{ + struct CustomEventContextFlag_t + {}; + inline constexpr CustomEventContextFlag_t CustomEventContextFlag{}; + + class ObservedBase + { + public: + explicit ObservedBase(EventContext* ctx) + : eventContext_{ctx} + , attachedEvents_{} + , attachedOneshotEvents_{} + {} + virtual ~ObservedBase() = default; + ObservedBase(ObservedBase const&) = delete; + ObservedBase(ObservedBase&& other) + : eventContext_{other.eventContext_} + , attachedEvents_{} + , attachedOneshotEvents_{} + { + // events are outside the value logic of the observed class. the contained value is moved, but the events + // are merged. + for (auto& event : other.attachedEvents_) + attachedEvents_.push_back(std::move(event)); + for (auto& event : other.attachedOneshotEvents_) + attachedOneshotEvents_.push_back(std::move(event)); + } + ObservedBase& operator=(ObservedBase const&) = delete; + ObservedBase& operator=(ObservedBase&& other) + { + eventContext_ = other.eventContext_; + other.eventContext_ = nullptr; + for (auto& event : other.attachedEvents_) + attachedEvents_.push_back(std::move(event)); + for (auto& event : other.attachedOneshotEvents_) + attachedOneshotEvents_.push_back(std::move(event)); + return *this; + } + + void attachEvent(EventContext::EventIdType eventId) const + { + attachedEvents_.emplace_back(eventId); + } + void attachOneshotEvent(EventContext::EventIdType eventId) const + { + attachedOneshotEvents_.emplace_back(eventId); + } + void detachEvent(EventContext::EventIdType eventId) const + { + attachedEvents_.erase( + std::remove(std::begin(attachedEvents_), std::end(attachedEvents_), eventId), + std::end(attachedEvents_)); + } + + std::size_t attachedEventCount() const + { + return attachedEvents_.size(); + } + std::size_t attachedOneshotEventCount() const + { + return attachedOneshotEvents_.size(); + } + std::size_t totalAttachedEventCount() const + { + return attachedEvents_.size() + attachedOneshotEvents_.size(); + } + + /** + * @brief You should never need to do this. + */ + void detachAllEvents() + { + attachedEvents_.clear(); + attachedOneshotEvents_.clear(); + } + + virtual void update(bool /*force*/ = false) const + { + assert(eventContext_ != nullptr); + + for (auto& event : attachedEvents_) + { + auto activationResult = eventContext_->activateEvent(event); + if (activationResult.found == false) + event = EventRegistry::invalidEventId; + } + for (auto& event : attachedOneshotEvents_) + eventContext_->activateEvent(event); + attachedOneshotEvents_.clear(); + attachedEvents_.erase( + std::remove(std::begin(attachedEvents_), std::end(attachedEvents_), EventRegistry::invalidEventId), + std::end(attachedEvents_)); + } + + void updateNow(bool force = false) const + { + assert(eventContext_ != nullptr); + + update(force); + eventContext_->executeActiveEventsImmediately(); + } + + protected: + EventContext* eventContext_; + mutable std::vector attachedEvents_; + mutable std::vector attachedOneshotEvents_; + }; + + template + class ModifiableObserved : public ObservedBase + { + public: + using value_type = ContainedT; + + public: + class ModificationProxy + { + public: + explicit ModificationProxy(ModifiableObserved& observed) + : observed_{observed} + , now_{false} + {} + explicit ModificationProxy(ModifiableObserved& observed, bool now) + : observed_{observed} + , now_{now} + {} + ~ModificationProxy() + { + try + { + if (now_) + observed_.updateNow(true); + else + observed_.update(true); + } + catch (...) + { + // TODO: log? + } + } + auto& value() + { + return observed_.contained_; + } + auto* operator->() + { + return &observed_.contained_; + } + auto& operator*() + { + return observed_.contained_; + } + operator ContainedT&() + { + return observed_.contained_; + } + + private: + ModifiableObserved& observed_; + bool now_; + }; + + public: + ModifiableObserved() + : ObservedBase{&globalEventContext} + , contained_{} + {} + ModifiableObserved(const ModifiableObserved&) = delete; + ModifiableObserved(ModifiableObserved&& other) + : ObservedBase{std::move(other)} + , contained_{std::move(other.contained_)} + { + update(); + }; + ModifiableObserved& operator=(const ModifiableObserved&) = delete; + ModifiableObserved& operator=(ModifiableObserved&& other) + { + if (this != &other) + { + ObservedBase::operator=(std::move(other)); + contained_ = std::move(other.contained_); + update(); + } + return *this; + }; + ModifiableObserved& operator=(ContainedT const& contained) + { + contained_ = contained; + update(); + return *this; + } + ModifiableObserved& operator=(ContainedT&& contained) + { + contained_ = std::move(contained); + update(); + return *this; + } + ~ModifiableObserved() = default; + + template + explicit ModifiableObserved(T&& t) + : ObservedBase{&globalEventContext} + , contained_{std::forward(t)} + {} + + explicit ModifiableObserved(EventContext* ctx) + : ObservedBase{ctx} + , contained_{} + {} + + /** + * @brief Assign a completely new value. + * + * @param t + * @return ModifiableObserved& + */ + template + ModifiableObserved& operator=(T&& t) + { + contained_ = std::forward(t); + update(); + return *this; + } + + template + requires PlusAssignable + ModifiableObserved& operator+=(U const& rhs) + { + this->contained_ += rhs; + return *this; + } + template + requires MinusAssignable + ModifiableObserved& operator-=(U const& rhs) + { + this->contained_ -= rhs; + return *this; + } + + template + requires std::equality_comparable_with && Fundamental && Fundamental + ModifiableObserved& operator=(T&& t) + { + return assignChecked(t); + } + + template + requires std::equality_comparable_with + ModifiableObserved& assignChecked(T&& other) + { + if (contained_ != other) + { + contained_ = std::forward(other); + update(); + } + return *this; + } + + /** + * @brief Can be used to make mutations to the underlying class that get commited when the returned proxy is + * destroyed. + * + * @return ModificationProxy + */ + ModificationProxy modify() + { + return ModificationProxy{*this}; + } + + ModificationProxy modifyNow() + { + return ModificationProxy{*this, true}; + } + + ContainedT& value() + { + return contained_; + } + ContainedT const& value() const + { + return contained_; + } + ContainedT& operator*() + { + return contained_; + } + ContainedT const& operator*() const + { + return contained_; + } + ContainedT* operator->() + { + return &contained_; + } + ContainedT const* operator->() const + { + return &contained_; + } + + /** + * @brief Sets the value without making an update. + */ + void assignWithoutUpdate(ContainedT&& t) + { + contained_ = std::forward(t); + } + + protected: + ContainedT contained_; + }; + + template + class ObservedContainer; + + namespace ContainerWrapUtility + { + template + class ReferenceWrapper + { + public: + ReferenceWrapper(ObservedContainer* owner, std::size_t pos, T& ref) + : owner_{owner} + , pos_{pos} + , ref_{ref} + {} + operator T&() + { + return ref_; + } + T& operator*() + { + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + return ref_; + } + T const& operator*() const + { + return ref_; + } + T* operator->() + { + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + return &ref_; + } + T const* operator->() const + { + return &ref_; + } + T& get() + { + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + return ref_; + } + T const& getReadonly() + { + return ref_; + } + void operator=(T&& val) + { + ref_ = std::move(val); + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + } + void operator=(T const& val) + { + ref_ = val; + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + } + + protected: + ObservedContainer* owner_; + std::size_t pos_; + T& ref_; + }; + template + class PointerWrapper + { + public: + PointerWrapper(ObservedContainer* owner, std::size_t pos, T* ptr) noexcept + : owner_{owner} + , pos_{pos} + , ptr_{ptr} + {} + operator T&() + { + return *ptr_; + } + T& operator*() + { + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + return *ptr_; + } + T const& operator*() const + { + return *ptr_; + } + T* operator->() + { + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + return ptr_; + } + T const* operator->() const + { + return ptr_; + } + T& get() + { + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + return *ptr_; + } + T const& getReadonly() + { + return *ptr_; + } + void operator=(T* ptr) + { + ptr_ = ptr; + owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); + } + + protected: + ObservedContainer* owner_; + std::size_t pos_; + T* ptr_; + }; + + template + class IteratorWrapper + { + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = typename WrappedIterator::value_type; + using difference_type = typename WrappedIterator::difference_type; + using pointer = PointerWrapper; + using reference = ReferenceWrapper; + + public: + IteratorWrapper(ObservedContainer* owner, WrappedIterator it) + : owner_{owner} + , it_{std::move(it)} + {} + IteratorWrapper& operator+=(difference_type n) + { + it_ += n; + return *this; + } + IteratorWrapper& operator-=(difference_type n) + { + it_ -= n; + return *this; + } + IteratorWrapper& operator++() + { + ++it_; + return *this; + } + IteratorWrapper operator++(int) + { + return IteratorWrapper{it_++}; + } + IteratorWrapper& operator--() + { + --it_; + return *this; + } + IteratorWrapper operator--(int) + { + return IteratorWrapper{it_--}; + } + friend IteratorWrapper operator+(IteratorWrapper const& wrap, difference_type n) + { + return IteratorWrapper{wrap.owner_, wrap.it_ + n}; + } + friend IteratorWrapper operator-(IteratorWrapper const& wrap, difference_type n) + { + return IteratorWrapper{wrap.owner_, wrap.it_ - n}; + } + difference_type operator-(IteratorWrapper const& other) const + { + return it_ - other.it_; + } + auto operator*() + { + if constexpr (std::is_same_v) + return ReferenceWrapper{ + owner_, static_cast(it_ - owner_->contained_.rbegin()), *it_}; + else + return ReferenceWrapper{ + owner_, static_cast(it_ - owner_->contained_.begin()), *it_}; + } + auto operator*() const + { + return *it_; + } + auto operator->() + { + if constexpr (std::is_same_v) + return PointerWrapper{ + owner_, static_cast(it_ - owner_->contained_.rbegin()), &*it_}; + else + return PointerWrapper{ + owner_, static_cast(it_ - owner_->contained_.begin()), &*it_}; + } + auto operator->() const + { + return &*it_; + } + IteratorWrapper operator[](std::size_t offset) const + { + return IteratorWrapper{owner_, it_[offset]}; + } + bool operator<(IteratorWrapper const& other) const + { + return it_ < other.it_; + } + bool operator>(IteratorWrapper const& other) const + { + return it_ > other.it_; + } + bool operator<=(IteratorWrapper const& other) const + { + return it_ <= other.it_; + } + bool operator>=(IteratorWrapper const& other) const + { + return it_ >= other.it_; + } + bool operator==(IteratorWrapper const& other) const + { + return it_ == other.it_; + } + WrappedIterator getWrapped() const + { + return it_; + } + + private: + ObservedContainer* owner_; + WrappedIterator it_; + }; + }; + + template + class ObservedContainer : public ModifiableObserved + { + public: + friend class ContainerWrapUtility::ReferenceWrapper; + friend class ContainerWrapUtility::PointerWrapper; + + using value_type = typename ContainerT::value_type; + using allocator_type = typename ContainerT::allocator_type; + using size_type = typename ContainerT::size_type; + using difference_type = typename ContainerT::difference_type; + using reference = ContainerWrapUtility::ReferenceWrapper; + using const_reference = typename ContainerT::const_reference; + using pointer = ContainerWrapUtility::PointerWrapper; + using const_pointer = typename ContainerT::const_pointer; + + using iterator = ContainerWrapUtility::IteratorWrapper; + using const_iterator = typename ContainerT::const_iterator; + using reverse_iterator = + ContainerWrapUtility::IteratorWrapper; + using const_reverse_iterator = typename ContainerT::const_reverse_iterator; + + using ModifiableObserved::contained_; + using ModifiableObserved::update; + + public: + explicit ObservedContainer(EventContext* ctx) + : ModifiableObserved{ctx} + , rangeContext_{0} + , afterEffectId_{registerAfterEffect()} + {} + explicit ObservedContainer() + : ModifiableObserved{} + , rangeContext_{0} + , afterEffectId_{registerAfterEffect()} + {} + template + explicit ObservedContainer(T&& t) + : ModifiableObserved{std::forward(t)} + , rangeContext_{static_cast(contained_.size())} + , afterEffectId_{registerAfterEffect()} + {} + explicit ObservedContainer(RangeEventContext&& rangeContext) + : ModifiableObserved{} + , rangeContext_{std::move(rangeContext)} + , afterEffectId_{registerAfterEffect()} + {} + template + ObservedContainer(T&& t, RangeEventContext&& rangeContext) + : ModifiableObserved{std::forward(t)} + , rangeContext_{std::move(rangeContext)} + , afterEffectId_{registerAfterEffect()} + {} + + ObservedContainer(const ObservedContainer&) = delete; + ObservedContainer(ObservedContainer&&) = default; + ObservedContainer& operator=(const ObservedContainer&) = delete; + ObservedContainer& operator=(ObservedContainer&&) = default; + ~ObservedContainer() = default; + + constexpr auto map(auto&& function) const; + constexpr auto map(auto&& function); + + template + ObservedContainer& operator=(T&& t) + { + contained_ = std::forward(t); + rangeContext_.reset(static_cast(contained_.size()), true); + update(); + return *this; + } + void assign(size_type count, const value_type& value) + { + contained_.assign(count, value); + rangeContext_.reset(static_cast(contained_.size()), true); + update(); + } + template + void assign(Iterator first, Iterator last) + { + contained_.assign(first, last); + rangeContext_.reset(static_cast(contained_.size()), true); + update(); + } + void assign(std::initializer_list ilist) + { + contained_.assign(ilist); + rangeContext_.reset(static_cast(contained_.size()), true); + update(); + } + + // Element access + reference front() + { + return reference{this, 0, contained_.front()}; + } + const_reference front() const + { + return contained_.front(); + } + reference back() + { + return reference{this, contained_.size() - 1, contained_.back()}; + } + const_reference back() const + { + return contained_.back(); + } + pointer data() noexcept + { + return pointer{this, 0, contained_.data()}; + } + const_pointer data() const noexcept + { + return contained_.data(); + } + reference at(size_type pos) + { + return reference{this, pos, contained_.at(pos)}; + } + const_reference at(size_type pos) const + { + return contained_.at(pos); + } + reference operator[](size_type pos) + { + return reference{this, pos, contained_[pos]}; + } + const_reference operator[](size_type pos) const + { + return contained_[pos]; + } + + // Iterators + iterator begin() noexcept + { + return iterator{this, contained_.begin()}; + } + const_iterator begin() const noexcept + { + return contained_.begin(); + } + iterator end() noexcept + { + return iterator{this, contained_.end()}; + } + const_iterator end() const noexcept + { + return contained_.end(); + } + const_iterator cbegin() const noexcept + { + return contained_.cbegin(); + } + const_iterator cend() const noexcept + { + return contained_.cend(); + } + reverse_iterator rbegin() noexcept + { + return reverse_iterator{this, contained_.rbegin()}; + } + const_reverse_iterator rbegin() const noexcept + { + return contained_.rbegin(); + } + reverse_iterator rend() noexcept + { + return reverse_iterator{this, contained_.rend()}; + } + const_reverse_iterator rend() const noexcept + { + return contained_.rend(); + } + const_reverse_iterator crbegin() const noexcept + { + return contained_.crbegin(); + } + const_reverse_iterator crend() const noexcept + { + return contained_.crend(); + } + + // Capacity + bool empty() const noexcept + { + return contained_.empty(); + } + std::size_t size() const noexcept + { + return contained_.size(); + } + template + Detail::PickFirst_t().max_size())> max_size() const noexcept + { + return contained_.max_size(); + } + template + Detail::PickFirst_t().reserve(std::declval()))> + reserve(size_type capacity) + { + return contained_.reserve(capacity); + } + template + Detail::PickFirst_t().capacity())> capacity() const noexcept + { + return contained_.capacity(); + } + template + Detail::PickFirst_t().shrink_to_fit())> shrink_to_fit() + { + return contained_.shrink_to_fit(); + } + + // Modifiers + void clear() + { + contained_.clear(); + rangeContext_.reset(0, true); + update(); + } + template + Detail::PickFirst_t< + std::pair, + decltype(std::declval().insert(std::declval()))> + insert(const value_type& value) + { + assert(ObservedBase::eventContext_ != nullptr); + + const auto result = contained_.insert(value); + rangeContext_.performFullRangeUpdate(); + update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + return result; + } + template + Detail::PickFirst_t< + std::pair, + decltype(std::declval().insert(std::declval()))> + insert(value_type&& value) + { + assert(ObservedBase::eventContext_ != nullptr); + + const auto result = contained_.insert(std::move(value)); + rangeContext_.performFullRangeUpdate(); + update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + return result; + } + iterator insert(iterator pos, const value_type& value) + { + return insert(pos.getWrapped(), value); + } + iterator insert(const_iterator pos, const value_type& value) + { + const auto distance = pos - cbegin(); + auto it = contained_.insert(pos, value); + insertRangeChecked(distance, distance, RangeStateType::Insert); + return iterator{this, it}; + } + iterator insert(iterator pos, value_type&& value) + { + return insert(pos.getWrapped(), std::move(value)); + } + iterator insert(const_iterator pos, value_type&& value) + { + const auto distance = pos - cbegin(); + auto it = contained_.insert(pos, std::move(value)); + insertRangeChecked(distance, distance, RangeStateType::Insert); + return iterator{this, it}; + } + iterator insert(iterator pos, size_type count, const value_type& value) + { + return insert(pos.getWrapped(), count, value); + } + iterator insert(const_iterator pos, size_type count, const value_type& value) + { + const auto distance = pos - cbegin(); + auto it = contained_.insert(pos, count, value); + insertRangeChecked(distance, distance + count, RangeStateType::Insert); + return iterator{this, it}; + } + template + iterator insert(iterator pos, Iterator first, Iterator last) + { + return insert(pos.getWrapped(), first, last); + } + template + iterator insert(const_iterator pos, Iterator first, Iterator last) + { + const auto distance = pos - cbegin(); + auto it = contained_.insert(pos, first, last); + insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Insert); + return iterator{this, it}; + } + iterator insert(iterator pos, std::initializer_list ilist) + { + return insert(pos.getWrapped(), ilist); + } + iterator insert(const_iterator pos, std::initializer_list ilist) + { + const auto distance = pos - cbegin(); + auto it = contained_.insert(pos, ilist); + insertRangeChecked(distance, distance + ilist.size(), RangeStateType::Insert); + return iterator{this, it}; + } + template + iterator emplace(const_iterator pos, Args&&... args) + { + const auto distance = pos - cbegin(); + auto it = contained_.emplace(pos, std::forward(args)...); + insertRangeChecked(distance, distance + sizeof...(Args), RangeStateType::Insert); + return iterator{this, it}; + } + iterator erase(iterator pos) + { + // TODO: move item to delete to end and then pop back? or is vector erase clever enough? + const auto distance = pos - begin(); + auto it = contained_.erase(pos.getWrapped()); + insertRangeChecked(distance, distance, RangeStateType::Erase); + return iterator{this, it}; + } + iterator erase(const_iterator pos) + { + const auto distance = pos - cbegin(); + auto it = contained_.erase(pos); + insertRangeChecked(distance, distance, RangeStateType::Erase); + return iterator{this, it}; + } + iterator erase(iterator first, iterator last) + { + const auto distance = first - begin(); + auto it = contained_.erase(first.getWrapped(), last.getWrapped()); + insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Erase); + return iterator{this, it}; + } + iterator erase(const_iterator first, const_iterator last) + { + const auto distance = first - cbegin(); + auto it = contained_.erase(first, last); + insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Erase); + return iterator{this, it}; + } + template + Detail::PickFirst_t().push_back(std::declval()))> + push_back(const value_type& value) + { + contained_.push_back(value); + insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); + } + template + Detail::PickFirst_t().push_back(std::declval()))> + push_back(value_type&& value) + { + contained_.push_back(std::move(value)); + insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); + } + template + Detail::PickFirst_t().push_front(std::declval()))> + push_front(const value_type& value) + { + contained_.push_front(value); + insertRangeChecked(0, 0, RangeStateType::Insert); + } + template + Detail::PickFirst_t().push_front(std::declval()))> + push_front(value_type&& value) + { + contained_.push_front(std::move(value)); + insertRangeChecked(0, 0, RangeStateType::Insert); + } + template + void emplace_back(Args&&... args) + { + contained_.emplace_back(std::forward(args)...); + insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); + } + template + Detail::PickFirst_t().emplace_front())> emplace_front(Args&&... args) + { + contained_.emplace_front(std::forward(args)...); + insertRangeChecked(0, 0, RangeStateType::Insert); + } + void pop_back() + { + contained_.pop_back(); + insertRangeChecked(size(), size(), RangeStateType::Erase); + } + template + Detail::PickFirst_t().pop_front())> pop_front() + { + contained_.pop_front(); + insertRangeChecked(0, 0, RangeStateType::Erase); + } + template + Detail::PickFirst_t().resize(std::declval()))> + resize(size_type count) + { + const auto sizeBefore = contained_.size(); + contained_.resize(count); + if (sizeBefore < count) + insertRangeChecked(sizeBefore, count, RangeStateType::Insert); + else + insertRangeChecked(count, sizeBefore, RangeStateType::Erase); + } + template + Detail::PickFirst_t< + void, + decltype(std::declval().resize(std::declval(), std::declval()))> + resize(size_type count, value_type const& fillValue) + { + const auto sizeBefore = contained_.size(); + contained_.resize(count, fillValue); + if (sizeBefore < count) + insertRangeChecked(sizeBefore, count, RangeStateType::Insert); + else + insertRangeChecked(count, sizeBefore, RangeStateType::Erase); + } + void swap(ContainerT& other) + { + contained_.swap(other); + rangeContext_.reset(contained_.size(), true); + update(); + } + + // Other + ContainerT& value() + { + return contained_; + } + ContainerT const& value() const + { + return contained_; + } + RangeEventContext& rangeContext() + { + return rangeContext_; + } + RangeEventContext const& rangeContext() const + { + return rangeContext_; + } + + protected: + void update(bool force = false) const override + { + if (force) + rangeContext_.reset(static_cast(contained_.size()), true); + ObservedBase::update(force); + } + + protected: + void insertRangeChecked(std::size_t low, std::size_t high, RangeStateType type) + { + std::function doInsert; + doInsert = [&](int retries) { + assert(ObservedBase::eventContext_ != nullptr); + + const auto result = rangeContext_.insertModificationRange(contained_.size(), low, high, type); + if (result == RangeEventContext::InsertResult::Final) + { + update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + } + else if (result == RangeEventContext::InsertResult::Retry) + { + if (retries < 3) + doInsert(retries + 1); + else + { + rangeContext_.reset(static_cast(contained_.size()), true); + update(); + ObservedBase::eventContext_->executeActiveEventsImmediately(); + return; + } + } + else + update(); + }; + + doInsert(0); + } + + auto registerAfterEffect() + { + assert(ObservedBase::eventContext_ != nullptr); + return ObservedBase::eventContext_->registerAfterEffect(Event{[this](EventContext::EventIdType) { + rangeContext_.reset(static_cast(contained_.size()), true); + return true; + }}); + } + + private: + mutable RangeEventContext rangeContext_; + mutable EventContext::EventIdType afterEffectId_; + }; + + template + class Observed : public ModifiableObserved + { + public: + using ModifiableObserved::ModifiableObserved; + using ModifiableObserved::operator=; + using ModifiableObserved::operator->; + + Observed& operator=(T const& contained) + { + ModifiableObserved::operator=(contained); + return *this; + } + Observed& operator=(T&& contained) + { + ModifiableObserved::operator=(std::move(contained)); + return *this; + } + }; + template + class Observed> : public ObservedContainer> + { + public: + using ObservedContainer>::ObservedContainer; + using ObservedContainer>::operator=; + using ObservedContainer>::operator->; + static constexpr auto isRandomAccess = true; + + Observed>& operator=(std::vector const& contained) + { + ObservedContainer>::operator=(contained); + return *this; + } + Observed>& operator=(std::vector&& contained) + { + ObservedContainer>::operator=(std::move(contained)); + return *this; + } + }; + template + class Observed> : public ObservedContainer> + { + public: + using ObservedContainer>::ObservedContainer; + using ObservedContainer>::operator=; + using ObservedContainer>::operator->; + static constexpr auto isRandomAccess = true; + + Observed>& operator=(std::deque const& contained) + { + ObservedContainer>::operator=(contained); + return *this; + } + Observed>& operator=(std::deque&& contained) + { + ObservedContainer>::operator=(std::move(contained)); + return *this; + } + }; + template + class Observed> : public ObservedContainer> + { + public: + using ObservedContainer>::ObservedContainer; + using ObservedContainer>::operator=; + using ObservedContainer>::operator->; + static constexpr auto isRandomAccess = true; + + Observed>& operator=(std::basic_string const& contained) + { + ObservedContainer>::operator=(contained); + return *this; + } + Observed>& operator=(std::basic_string&& contained) + { + ObservedContainer>::operator=(std::move(contained)); + return *this; + } + + Observed>& erase(std::size_t index = 0, std::size_t count = std::string::npos) + { + const auto sizeBefore = this->contained_.size(); + this->contained_.erase(index, count); + this->insertRangeChecked(index, sizeBefore, RangeStateType::Erase); + return *this; + } + }; + template + class Observed> : public ObservedContainer> + { + public: + using ObservedContainer>::ObservedContainer; + using ObservedContainer>::operator=; + using ObservedContainer>::operator->; + static constexpr auto isRandomAccess = false; + + public: + Observed() + : ObservedContainer>{RangeEventContext{0, true}} + {} + template > + explicit Observed(T&& t) + : ObservedContainer>{ + std::forward(t), + RangeEventContext{static_cast(t.size()), true}} + {} + + Observed>& operator=(std::set const& contained) + { + ObservedContainer>::operator=(contained); + return *this; + } + Observed>& operator=(std::set&& contained) + { + ObservedContainer>::operator=(std::move(contained)); + return *this; + } + }; + template + class Observed> : public ObservedContainer> + { + public: + using ObservedContainer>::ObservedContainer; + using ObservedContainer>::operator=; + using ObservedContainer>::operator->; + static constexpr auto isRandomAccess = false; + + public: + Observed() + : ObservedContainer>{RangeEventContext{0, true}} + {} + template > + explicit Observed(T&& t) + : ObservedContainer>{ + std::forward(t), + RangeEventContext{static_cast(t.size()), true}} + {} + + Observed>& operator=(std::list const& contained) + { + ObservedContainer>::operator=(contained); + return *this; + } + Observed>& operator=(std::list&& contained) + { + ObservedContainer>::operator=(std::move(contained)); + return *this; + } + }; + + template <> + class Observed : public ObservedBase + { + public: + using ObservedBase::ObservedBase; + using ObservedBase::operator=; + + void modify() const + { + update(); + }; + + void modifyNow() const + { + updateNow(); + }; + }; + + namespace Detail + { + template + struct IsObserved + { + static constexpr bool value = false; + }; + + template + struct IsObserved> + { + static constexpr bool value = true; + }; + } + + template + requires Incrementable + inline ModifiableObserved& operator++(ModifiableObserved& observedValue) + { + ++observedValue.value(); + observedValue.update(); + return observedValue; + } + template + requires Incrementable + inline T operator++(ModifiableObserved& observedValue, int) + { + auto tmp = observedValue.value(); + ++observedValue.value(); + observedValue.update(); + return tmp; + } + + template + inline auto operator--(ModifiableObserved& observedValue) + -> ModifiableObserved())>>& + { + --observedValue.value(); + observedValue.update(); + return observedValue; + } + template + inline auto + operator--(ModifiableObserved& observedValue, int) -> Detail::PickFirst_t()--)> + { + auto tmp = observedValue.value(); + --observedValue.value(); + observedValue.update(); + return tmp; + } + + template + concept IsObserved = Detail::IsObserved>::value; + + namespace Detail + { + template + struct CopyableObservedWrap // minimal wrapper to make Observed copiable + { + public: + explicit constexpr CopyableObservedWrap(Observed const& observed) + : observed_{&observed} + {} + + inline T value() const + { + return observed_->value(); + } + + inline void attachEvent(auto eventId) const + { + observed_->attachEvent(eventId); + } + + inline void detachEvent(auto eventId) const + { + observed_->detachEvent(eventId); + } + + private: + Observed const* observed_; + }; + } +} \ No newline at end of file diff --git a/nui/include/nui/event_system/observed_value_combinator.hpp b/nui/include/nui/event_system/observed_value_combinator.hpp new file mode 100644 index 00000000..1205e7cb --- /dev/null +++ b/nui/include/nui/event_system/observed_value_combinator.hpp @@ -0,0 +1,149 @@ +#pragma once + +#include +#include +#include + +#include + +namespace Nui +{ + template + class ObservedValueCombinatorBase + { + public: + explicit constexpr ObservedValueCombinatorBase(ObservedValues const&... observedValues) + : observedValues_{observedValues...} + {} + explicit constexpr ObservedValueCombinatorBase(std::tuple observedValues) + : observedValues_{std::move(observedValues)} + {} + + constexpr void attachEvent(auto eventId) const + { + tupleForEach(observedValues_, [eventId](auto const& observed) { + observed.attachEvent(eventId); + }); + } + + constexpr void attachOneshotEvent(auto eventId) const + { + tupleForEach(observedValues_, [eventId](auto const& observed) { + observed.attachOneshotEvent(eventId); + }); + } + + constexpr void detachEvent(auto eventId) const + { + tupleForEach(observedValues_, [eventId](auto const& observed) { + observed.detachEvent(eventId); + }); + } + + std::tuple const& observedValues() & + { + return observedValues_; + } + + std::tuple&& observedValues() && + { + return std::move(const_cast&>(observedValues_)); + } + + protected: + const std::tuple observedValues_; + }; + template + ObservedValueCombinatorBase(std::tuple) -> ObservedValueCombinatorBase; + template + ObservedValueCombinatorBase(ObservedValues const&...) -> ObservedValueCombinatorBase; + + template + class ObservedValueCombinator; + + template + class ObservedValueCombinatorWithGenerator : public ObservedValueCombinatorBase + { + public: + constexpr ObservedValueCombinatorWithGenerator( + std::tuple observedValues, + RendererType generator) + : ObservedValueCombinatorBase{std::move(observedValues)} + , generator_{std::move(generator)} + {} + + ObservedValueCombinator split() && + { + return ObservedValueCombinator{std::move(this->observedValues_)}; + } + + constexpr auto value() const + { + return generator_(); + } + + RendererType generator() const& + { + return generator_; + } + RendererType generator() && + { + return std::move(generator_); + } + + protected: + const RendererType generator_; + }; + + template + class ObservedValueCombinatorWithPropertyGenerator + : public ObservedValueCombinatorWithGenerator + { + public: + using ObservedValueCombinatorWithGenerator:: + ObservedValueCombinatorWithGenerator; + ObservedValueCombinatorWithPropertyGenerator( + ObservedValueCombinatorWithGenerator&& other) + : ObservedValueCombinatorWithGenerator{std::move(other)} + {} + + using ObservedValueCombinatorWithGenerator::split; + using ObservedValueCombinatorWithGenerator::value; + using ObservedValueCombinatorWithGenerator::generator; + }; + + template + class ObservedValueCombinator : public ObservedValueCombinatorBase + { + public: + using ObservedValueCombinatorBase::ObservedValueCombinatorBase; + using ObservedValueCombinatorBase::observedValues_; + + template + requires std::invocable + constexpr ObservedValueCombinatorWithGenerator + generate(RendererType&& generator) + { + return ObservedValueCombinatorWithGenerator{ + observedValues_, std::forward(generator)}; + } + + template + requires std::invocable + constexpr ObservedValueCombinatorWithPropertyGenerator + generateProperty(RendererType&& generator) + { + return ObservedValueCombinatorWithPropertyGenerator{ + observedValues_, std::forward(generator)}; + } + }; + template + ObservedValueCombinator(ObservedValues const&...) -> ObservedValueCombinator; + + template + requires(Detail::IsObserved::value && ...) + ObservedValueCombinator observe(ObservedValues const&... observedValues) + { + return ObservedValueCombinator(observedValues...); + } +} \ No newline at end of file diff --git a/nui/include/nui/event_system/range.hpp b/nui/include/nui/event_system/range.hpp new file mode 100644 index 00000000..4759225c --- /dev/null +++ b/nui/include/nui/event_system/range.hpp @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#ifdef NUI_HAS_STD_RANGES +# include +#endif + +namespace Nui +{ + template + class ObservedRange + { + public: + using ObservedType = ObservedValue; + + static constexpr bool isRandomAccess = ObservedType::isRandomAccess; + + explicit constexpr ObservedRange(ObservedValue& observedValues) + : observedValue_{observedValues} + {} + + ObservedValue const& underlying() const + { + return observedValue_; + } + ObservedValue& underlying() + requires(!std::is_const_v) + { + return observedValue_; + } + + private: + ObservedValue& observedValue_; + }; + + template + class UnoptimizedRange + { + public: + UnoptimizedRange(ObservedValueCombinator&& observedValues, CopyableRangeLike&& rangeLike) + : observedValues_{std::move(observedValues)} + , rangeLike_{std::move(rangeLike)} + {} + UnoptimizedRange(CopyableRangeLike&& rangeLike) + requires(sizeof...(ObservedValues) == 0) + : observedValues_{} + , rangeLike_{std::move(rangeLike)} + {} + + auto begin() const + { + return rangeLike_.begin(); + } + auto end() const + { + return rangeLike_.end(); + } + + ObservedValueCombinator const& underlying() const + { + return observedValues_; + } + ObservedValueCombinator& underlying() + { + return observedValues_; + } + + private: + ObservedValueCombinator observedValues_; + CopyableRangeLike rangeLike_; + }; + + template + requires(IsObserved) + ObservedRange range(ObservedValue& observedValues) + { + return ObservedRange{observedValues}; + } + + template + requires(IsObserved) + ObservedRange range(ObservedValue const& observedValues) + { + return ObservedRange{observedValues}; + } + + template + UnoptimizedRange, Observed...> + range(ContainerT const& container, Observed const&... observed) + { + return UnoptimizedRange, Observed...>{ + ObservedValueCombinator{observed...}, + IteratorAccessor{container}, + }; + } + + template + UnoptimizedRange, Observed...> + range(ContainerT& container, Observed const&... observed) + { + return UnoptimizedRange, Observed...>{ + ObservedValueCombinator{observed...}, + IteratorAccessor{container}, + }; + } + + template + UnoptimizedRange, Observed...> + range(ContainerT const& container, Observed&... observed) + { + return UnoptimizedRange, Observed...>{ + ObservedValueCombinator{observed...}, + IteratorAccessor{container}, + }; + } + + template + UnoptimizedRange, Observed...> range(ContainerT& container, Observed&... observed) + { + return UnoptimizedRange, Observed...>{ + ObservedValueCombinator{observed...}, + IteratorAccessor{container}, + }; + } + + template + UnoptimizedRange> range(ContainerT& container) + { + return UnoptimizedRange>{ + IteratorAccessor{container}, + }; + } + + template + UnoptimizedRange> range(ContainerT const& container) + { + return UnoptimizedRange>{ + IteratorAccessor{container}, + }; + } + +#ifdef NUI_HAS_STD_RANGES + template + UnoptimizedRange>, Observed...> + range(T const& container, Observed const&... observed) + { + return UnoptimizedRange>, Observed...>{ + ObservedValueCombinator{observed...}, + std::ranges::subrange>{ + std::ranges::begin(container), std::ranges::end(container)}, + }; + } +#endif + + template + constexpr auto ObservedContainer::map(auto&& function) const + { + return std::pair>, std::decay_t>{ + ObservedRange>{static_cast const&>(*this)}, + std::forward>(function), + }; + } + + template + constexpr auto ObservedContainer::map(auto&& function) + { + return std::pair>, std::decay_t>{ + ObservedRange>{static_cast&>(*this)}, + std::forward>(function), + }; + } +} \ No newline at end of file diff --git a/nui/include/nui/event_system/range_event_context.hpp b/nui/include/nui/event_system/range_event_context.hpp new file mode 100644 index 00000000..76cdca5f --- /dev/null +++ b/nui/include/nui/event_system/range_event_context.hpp @@ -0,0 +1,295 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace Nui +{ + enum RangeStateType + { + Keep = 0b0001, + Modify = 0b0010, + Insert = 0b0100, + Erase = 0b1000, + Extended = 0b0001'0000 + }; + + namespace Detail + { + template + class RangeStateInterval; + + template + std::vector> cutIntervals( + RangeStateInterval const& k, + RangeStateInterval const& m); + + template + class RangeStateInterval + { + public: + using value_type = ValueType; + using interval_kind = IntervalKind; + + public: + RangeStateInterval(value_type low, value_type high, RangeStateType type) + : low_{low} + , high_{high} + , type_{type} + {} + void reset(value_type low, value_type high, RangeStateType type) + { + low_ = low; + high_ = high; + type_ = type; + } + friend bool operator==(RangeStateInterval const& lhs, RangeStateInterval const& rhs) + { + return lhs.start_ == rhs.start_ && lhs.end_ == rhs.end_ && lhs.type_ == rhs.type_; + } + friend bool operator!=(RangeStateInterval const& lhs, RangeStateInterval const& rhs) + { + return !(lhs == rhs); + } + value_type low() const + { + return low_; + } + value_type high() const + { + return high_; + } + bool overlaps(value_type l, value_type h) const + { + return low_ <= h && l <= high_; + } + bool overlaps_exclusive(value_type l, value_type h) const + { + return low_ < h && l < high_; + } + bool overlaps(RangeStateInterval const& other) const + { + return overlaps(other.low_, other.high_); + } + bool overlapsOrIsAdjacent(RangeStateInterval const& other) const + { + return low_ <= other.high_ + 1 && other.low_ - 1 <= high_; + } + bool overlaps_exclusive(RangeStateInterval const& other) const + { + return overlaps_exclusive(other.low_, other.high_); + } + bool within(value_type value) const + { + return interval_kind::within(low_, high_, value); + } + bool within(RangeStateInterval const& other) const + { + return low_ <= other.low_ && high_ >= other.high_; + } + value_type operator-(RangeStateInterval const& other) const + { + if (overlaps(other)) + return 0; + if (high_ < other.low_) + return other.low_ - high_; + else + return low_ - other.high_; + } + value_type size() const + { + return high_ - low_; + } + std::vector join(RangeStateInterval const& other) const + { + auto typeWithoutExtension = static_cast(other.type_ & ~RangeStateType::Extended); + long extensionFix = other.type_ & RangeStateType::Extended ? 1 : 0; + switch (typeWithoutExtension | type_) + { + case (RangeStateType::Modify | RangeStateType::Modify): + { + return { + {std::min(low_, other.low_ + extensionFix), + std::max(high_, other.high_ - extensionFix), + RangeStateType::Modify}}; + } + case (RangeStateType::Keep | RangeStateType::Modify): + { + // Modifications cut out of the keep part. + return cutIntervals( + *this, {other.low_ + extensionFix, other.high_ - extensionFix, typeWithoutExtension}); + } + default: + { + throw std::runtime_error("Invalid insertion case"); + } + } + } + RangeStateType type() const noexcept + { + return type_; + } + + private: + value_type low_; + value_type high_; + RangeStateType type_; + }; + + // stream iterator for intervals + template + std::ostream& operator<<(std::ostream& os, RangeStateInterval const& interval) + { + os << "[" << interval.low() << ", " << interval.high() << "]"; + switch (interval.type()) + { + case RangeStateType::Keep: + os << "k"; + break; + case RangeStateType::Modify: + os << "m"; + break; + case RangeStateType::Insert: + os << "i"; + break; + default: + os << "?"; + break; + } + return os; + } + + template + std::vector> cutIntervals( + RangeStateInterval const& k, + RangeStateInterval const& m) + { + std::vector> result; + if (k.low() <= m.low() - 1) + result.emplace_back(k.low(), m.low() - 1, RangeStateType::Keep); + result.emplace_back(m.low(), m.high(), RangeStateType::Modify); + if (k.high() >= m.high() + 1) + result.emplace_back(m.high() + 1, k.high(), RangeStateType::Keep); + return result; + } + } + + class RangeEventContext + { + public: + explicit RangeEventContext(long dataSize) + : modificationRanges_{} + , fullRangeUpdate_{true} + , disableOptimizations_{false} + { + reset(dataSize, true); + } + RangeEventContext(long dataSize, bool disableOptimizations) + : modificationRanges_{} + , fullRangeUpdate_{true} + , disableOptimizations_{disableOptimizations} + { + reset(dataSize, true); + } + enum class InsertResult + { + Final, // Final, cannot accept further updates, must update immediately + Accepted, // Accepted, update can be deferred. + Retry // Must update immediately and reissue this. + }; + void performFullRangeUpdate() + { + fullRangeUpdate_ = true; + } + InsertResult insertModificationRange(long elementCount, long low, long high, RangeStateType type) + { + if (disableOptimizations_) + { + fullRangeUpdate_ = true; + return InsertResult::Final; + } + if (type == RangeStateType::Erase) + { + // TODO: optimize erase like insert. + reset(elementCount, true); + return InsertResult::Final; + } + else if (type == RangeStateType::Insert) + { + if (modificationRanges_.size() > 1) + return InsertResult::Retry; + if (!insertInterval_) + { + insertInterval_ = {low, high, type}; + return InsertResult::Accepted; + } + else + { + if (insertInterval_->overlapsOrIsAdjacent({low, high, type})) + { + auto lowmin = std::min(low, insertInterval_->low()); + insertInterval_->reset(lowmin, lowmin + insertInterval_->size() + (high - low + 1), type); + return InsertResult::Accepted; + } + else + { + if (high < insertInterval_->low()) + { + const auto size = high - low + 1; + insertInterval_->reset( + insertInterval_->low() + size, insertInterval_->high() + size, insertInterval_->type()); + } + return InsertResult::Retry; + } + } + } + if (insertInterval_) + return InsertResult::Retry; + + auto iter = modificationRanges_.insert_overlap( + {low - 1, high + 1, static_cast(type | RangeStateType::Extended)}, false, true); + return InsertResult::Accepted; + } + InsertResult + insertModificationRange(std::size_t elementCount, std::size_t low, std::size_t high, RangeStateType type) + { + return insertModificationRange( + static_cast(elementCount), static_cast(low), static_cast(high), type); + } + void reset(long dataSize, bool requireFullRangeUpdate) + { + modificationRanges_.clear(); + if (dataSize > 0) + modificationRanges_.insert({0l, dataSize - 1, RangeStateType::Keep}); + insertInterval_ = std::nullopt; + fullRangeUpdate_ = requireFullRangeUpdate; + } + bool isFullRangeUpdate() const noexcept + { + return fullRangeUpdate_; + } + std::optional> const& insertInterval() const + { + return insertInterval_; + } + auto begin() const + { + return modificationRanges_.begin(); + } + auto end() const + { + return modificationRanges_.end(); + } + + private: + lib_interval_tree::interval_tree> modificationRanges_; + std::optional> insertInterval_; + bool fullRangeUpdate_; + bool disableOptimizations_; + }; +} \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/event.hpp b/nui/include/nui/frontend/event_system/event.hpp index 5ab1b8a3..0991d531 100644 --- a/nui/include/nui/frontend/event_system/event.hpp +++ b/nui/include/nui/frontend/event_system/event.hpp @@ -1,67 +1,4 @@ -#pragma once - -#include -#include -#include - -namespace Nui -{ - struct EventImpl - { - virtual bool call(std::size_t eventId) = 0; - virtual bool valid() const = 0; - virtual ~EventImpl() = default; - }; - - struct TwoFunctorEventImpl : public EventImpl - { - TwoFunctorEventImpl(std::function action, std::function valid) - : action_{std::move(action)} - , valid_{std::move(valid)} - {} - - bool call(std::size_t eventId) override - { - auto result = action_(eventId); - return result; - } - - bool valid() const override - { - return valid_(); - } - - private: - std::function action_; - std::function valid_; - }; - - class Event - { - public: - Event( - std::function action, - std::function valid = - [] { - return true; - }) - : impl_{std::make_unique(std::move(action), std::move(valid))} - {} - Event(Event const&) = delete; - Event(Event&&) = default; - Event& operator=(Event const&) = delete; - Event& operator=(Event&&) = default; - - operator bool() const - { - return impl_->valid(); - } - bool operator()(std::size_t eventId) const - { - return impl_->call(eventId); - } - - private: - std::unique_ptr impl_; - }; -} \ No newline at end of file +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/event_context.hpp b/nui/include/nui/frontend/event_system/event_context.hpp index 8275c0d7..b3d9ad9b 100644 --- a/nui/include/nui/frontend/event_system/event_context.hpp +++ b/nui/include/nui/frontend/event_system/event_context.hpp @@ -1,85 +1,4 @@ -#pragma once - -#include - -#include - -namespace Nui -{ - class EventEngine - { - public: - EventEngine() = default; - EventEngine(const EventEngine&) = default; - EventEngine(EventEngine&&) = default; - EventEngine& operator=(const EventEngine&) = default; - EventEngine& operator=(EventEngine&&) = default; - virtual ~EventEngine() = default; - - virtual EventRegistry& eventRegistry() = 0; - }; - - class DefaultEventEngine : public EventEngine - { - public: - EventRegistry& eventRegistry() override - { - return eventRegistry_; - } - - private: - EventRegistry eventRegistry_; - }; - - /** - * @brief This object can be copied with low cost. - */ - class EventContext - { - public: - using EventIdType = EventRegistry::EventIdType; - - EventContext() - : impl_{std::make_shared()} - {} - EventContext(EventContext const&) = default; - EventContext(EventContext&&) = default; - EventContext& operator=(EventContext const&) = default; - EventContext& operator=(EventContext&&) = default; - ~EventContext() = default; - - EventIdType registerEvent(Event event) - { - return impl_->eventRegistry().registerEvent(std::move(event)); - } - auto activateEvent(EventIdType id) - { - return impl_->eventRegistry().activateEvent(id); - } - void executeActiveEventsImmediately() - { - impl_->eventRegistry().executeActiveEvents(); - } - void executeEvent(EventIdType id) - { - impl_->eventRegistry().executeEvent(id); - } - EventIdType registerAfterEffect(Event event) - { - return impl_->eventRegistry().registerAfterEffect(std::move(event)); - } - void cleanInvalidEvents() - { - impl_->eventRegistry().cleanInvalidEvents(); - } - void reset() - { - impl_->eventRegistry().clear(); - } - - private: - std::shared_ptr impl_; - }; - - extern thread_local EventContext globalEventContext; -} \ No newline at end of file +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/event_registry.hpp b/nui/include/nui/frontend/event_system/event_registry.hpp index 45430fcd..a518d21d 100644 --- a/nui/include/nui/frontend/event_system/event_registry.hpp +++ b/nui/include/nui/frontend/event_system/event_registry.hpp @@ -1,98 +1,4 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace Nui -{ - class EventRegistry - { - public: - using RegistryType = SelectablesRegistry; - using EventIdType = SelectablesRegistry::IdType; - constexpr static EventIdType invalidEventId = std::numeric_limits::max(); - - public: - EventRegistry() = default; - EventRegistry(const EventRegistry&) = delete; - EventRegistry(EventRegistry&&) = default; - EventRegistry& operator=(const EventRegistry&) = delete; - EventRegistry& operator=(EventRegistry&&) = default; - ~EventRegistry() = default; - - EventIdType registerEvent(Event event) - { - return registry_.append(std::move(event)); - } - - /** - * @brief Returns a pointer to the selected event (only valid until the next activation or event execution). May - * return nullptr when the event id was not found. - * - * @param id - * @return auto* - */ - RegistryType::SelectionResult activateEvent(EventIdType id) - { - return registry_.select(id); - } - - EventIdType registerAfterEffect(Event event) - { - return afterEffects_.append(std::move(event)); - } - - void executeEvent(EventIdType id) - { - registry_.deselect(id, [](SelectablesRegistry::ItemWithId const& itemWithId) -> bool { - if (!itemWithId.item) - return false; - return itemWithId.item.value()(itemWithId.id); - }); - } - - void executeActiveEvents() - { - registry_.deselectAll([](SelectablesRegistry::ItemWithId const& itemWithId) -> bool { - if (!itemWithId.item) - return false; - return itemWithId.item.value()(itemWithId.id); - }); - afterEffects_.deselectAll([](SelectablesRegistry::ItemWithId const& itemWithId) -> bool { - if (!itemWithId.item) - return false; - return itemWithId.item.value()(itemWithId.id); - }); - } - - void cleanInvalidEvents() - { - std::vector invalidIds; - for (auto const& itemWithId : registry_.rawRange()) - { - if (!itemWithId.item) - continue; - if (!static_cast(itemWithId.item.value())) - invalidIds.push_back(itemWithId.id); - } - - for (auto const& id : invalidIds) - registry_.erase(id); - } - - void clear() - { - registry_.clear(); - afterEffects_.clear(); - } - - private: - RegistryType registry_; - RegistryType afterEffects_; - }; -} \ No newline at end of file +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/observed_value.hpp b/nui/include/nui/frontend/event_system/observed_value.hpp index 890bb398..ba060b67 100644 --- a/nui/include/nui/frontend/event_system/observed_value.hpp +++ b/nui/include/nui/frontend/event_system/observed_value.hpp @@ -1,1241 +1,4 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Nui -{ - class ObservedBase - { - public: - ObservedBase() = default; - virtual ~ObservedBase() = default; - ObservedBase(ObservedBase const&) = delete; - ObservedBase(ObservedBase&& other) - : attachedEvents_{} - , attachedOneshotEvents_{} - { - // events are outside the value logic of the observed class. the contained value is moved, but the events - // are merged. - for (auto& event : other.attachedEvents_) - attachedEvents_.push_back(std::move(event)); - for (auto& event : other.attachedOneshotEvents_) - attachedOneshotEvents_.push_back(std::move(event)); - } - ObservedBase& operator=(ObservedBase const&) = delete; - ObservedBase& operator=(ObservedBase&& other) - { - for (auto& event : other.attachedEvents_) - attachedEvents_.push_back(std::move(event)); - for (auto& event : other.attachedOneshotEvents_) - attachedOneshotEvents_.push_back(std::move(event)); - return *this; - } - - void attachEvent(EventContext::EventIdType eventId) const - { - attachedEvents_.emplace_back(eventId); - } - void attachOneshotEvent(EventContext::EventIdType eventId) const - { - attachedOneshotEvents_.emplace_back(eventId); - } - void detachEvent(EventContext::EventIdType eventId) const - { - attachedEvents_.erase( - std::remove(std::begin(attachedEvents_), std::end(attachedEvents_), eventId), - std::end(attachedEvents_)); - } - - std::size_t attachedEventCount() const - { - return attachedEvents_.size(); - } - std::size_t attachedOneshotEventCount() const - { - return attachedOneshotEvents_.size(); - } - std::size_t totalAttachedEventCount() const - { - return attachedEvents_.size() + attachedOneshotEvents_.size(); - } - - /** - * @brief You should never need to do this. - */ - void detachAllEvents() - { - attachedEvents_.clear(); - attachedOneshotEvents_.clear(); - } - - virtual void update(bool /*force*/ = false) const - { - for (auto& event : attachedEvents_) - { - auto activationResult = globalEventContext.activateEvent(event); - if (activationResult.found == false) - event = EventRegistry::invalidEventId; - } - for (auto& event : attachedOneshotEvents_) - globalEventContext.activateEvent(event); - attachedOneshotEvents_.clear(); - attachedEvents_.erase( - std::remove(std::begin(attachedEvents_), std::end(attachedEvents_), EventRegistry::invalidEventId), - std::end(attachedEvents_)); - } - - protected: - mutable std::vector attachedEvents_; - mutable std::vector attachedOneshotEvents_; - }; - - template - class ModifiableObserved : public ObservedBase - { - public: - using value_type = ContainedT; - - public: - class ModificationProxy - { - public: - explicit ModificationProxy(ModifiableObserved& observed) - : observed_{observed} - {} - ~ModificationProxy() - { - try - { - observed_.update(true); - } - catch (...) - { - // TODO: log? - } - } - auto& value() - { - return observed_.contained_; - } - auto* operator->() - { - return &observed_.contained_; - } - auto& operator*() - { - return observed_.contained_; - } - operator ContainedT&() - { - return observed_.contained_; - } - - private: - ModifiableObserved& observed_; - }; - - public: - ModifiableObserved() = default; - ModifiableObserved(const ModifiableObserved&) = delete; - ModifiableObserved(ModifiableObserved&& other) - : ObservedBase{std::move(other)} - , contained_{std::move(other.contained_)} - { - update(); - }; - ModifiableObserved& operator=(const ModifiableObserved&) = delete; - ModifiableObserved& operator=(ModifiableObserved&& other) - { - if (this != &other) - { - ObservedBase::operator=(std::move(other)); - contained_ = std::move(other.contained_); - update(); - } - return *this; - }; - ModifiableObserved& operator=(ContainedT const& contained) - { - contained_ = contained; - update(); - return *this; - } - ModifiableObserved& operator=(ContainedT&& contained) - { - contained_ = std::move(contained); - update(); - return *this; - } - ~ModifiableObserved() = default; - - template - explicit ModifiableObserved(T&& t) - : contained_{std::forward(t)} - {} - - /** - * @brief Assign a completely new value. - * - * @param t - * @return ModifiableObserved& - */ - template - ModifiableObserved& operator=(T&& t) - { - contained_ = std::forward(t); - update(); - return *this; - } - - template - requires PlusAssignable - ModifiableObserved& operator+=(U const& rhs) - { - this->contained_ += rhs; - return *this; - } - template - requires MinusAssignable - ModifiableObserved& operator-=(U const& rhs) - { - this->contained_ -= rhs; - return *this; - } - - template - requires std::equality_comparable_with && Fundamental && Fundamental - ModifiableObserved& operator=(T&& t) - { - return assignChecked(t); - } - - template - requires std::equality_comparable_with - ModifiableObserved& assignChecked(T&& other) - { - if (contained_ != other) - { - contained_ = std::forward(other); - update(); - } - return *this; - } - - /** - * @brief Can be used to make mutations to the underlying class that get commited when the returned proxy is - * destroyed. - * - * @return ModificationProxy - */ - ModificationProxy modify() - { - return ModificationProxy{*this}; - } - - ContainedT& value() - { - return contained_; - } - ContainedT const& value() const - { - return contained_; - } - ContainedT& operator*() - { - return contained_; - } - ContainedT const& operator*() const - { - return contained_; - } - ContainedT* operator->() - { - return &contained_; - } - ContainedT const* operator->() const - { - return &contained_; - } - - /** - * @brief Sets the value without making an update. - */ - void assignWithoutUpdate(ContainedT&& t) - { - contained_ = std::forward(t); - } - - protected: - ContainedT contained_; - }; - - template - class ObservedContainer; - - namespace ContainerWrapUtility - { - template - class ReferenceWrapper - { - public: - ReferenceWrapper(ObservedContainer* owner, std::size_t pos, T& ref) - : owner_{owner} - , pos_{pos} - , ref_{ref} - {} - operator T&() - { - return ref_; - } - T& operator*() - { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return ref_; - } - T const& operator*() const - { - return ref_; - } - T* operator->() - { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return &ref_; - } - T const* operator->() const - { - return &ref_; - } - T& get() - { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return ref_; - } - T const& getReadonly() - { - return ref_; - } - void operator=(T&& val) - { - ref_ = std::move(val); - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - } - void operator=(T const& val) - { - ref_ = val; - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - } - - protected: - ObservedContainer* owner_; - std::size_t pos_; - T& ref_; - }; - template - class PointerWrapper - { - public: - PointerWrapper(ObservedContainer* owner, std::size_t pos, T* ptr) noexcept - : owner_{owner} - , pos_{pos} - , ptr_{ptr} - {} - operator T&() - { - return *ptr_; - } - T& operator*() - { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return *ptr_; - } - T const& operator*() const - { - return *ptr_; - } - T* operator->() - { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return ptr_; - } - T const* operator->() const - { - return ptr_; - } - T& get() - { - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - return *ptr_; - } - T const& getReadonly() - { - return *ptr_; - } - void operator=(T* ptr) - { - ptr_ = ptr; - owner_->insertRangeChecked(pos_, pos_, RangeStateType::Modify); - } - - protected: - ObservedContainer* owner_; - std::size_t pos_; - T* ptr_; - }; - - template - class IteratorWrapper - { - public: - using iterator_category = std::random_access_iterator_tag; - using value_type = typename WrappedIterator::value_type; - using difference_type = typename WrappedIterator::difference_type; - using pointer = PointerWrapper; - using reference = ReferenceWrapper; - - public: - IteratorWrapper(ObservedContainer* owner, WrappedIterator it) - : owner_{owner} - , it_{std::move(it)} - {} - IteratorWrapper& operator+=(difference_type n) - { - it_ += n; - return *this; - } - IteratorWrapper& operator-=(difference_type n) - { - it_ -= n; - return *this; - } - IteratorWrapper& operator++() - { - ++it_; - return *this; - } - IteratorWrapper operator++(int) - { - return IteratorWrapper{it_++}; - } - IteratorWrapper& operator--() - { - --it_; - return *this; - } - IteratorWrapper operator--(int) - { - return IteratorWrapper{it_--}; - } - friend IteratorWrapper operator+(IteratorWrapper const& wrap, difference_type n) - { - return IteratorWrapper{wrap.owner_, wrap.it_ + n}; - } - friend IteratorWrapper operator-(IteratorWrapper const& wrap, difference_type n) - { - return IteratorWrapper{wrap.owner_, wrap.it_ - n}; - } - difference_type operator-(IteratorWrapper const& other) const - { - return it_ - other.it_; - } - auto operator*() - { - if constexpr (std::is_same_v) - return ReferenceWrapper{ - owner_, static_cast(it_ - owner_->contained_.rbegin()), *it_}; - else - return ReferenceWrapper{ - owner_, static_cast(it_ - owner_->contained_.begin()), *it_}; - } - auto operator*() const - { - return *it_; - } - auto operator->() - { - if constexpr (std::is_same_v) - return PointerWrapper{ - owner_, static_cast(it_ - owner_->contained_.rbegin()), &*it_}; - else - return PointerWrapper{ - owner_, static_cast(it_ - owner_->contained_.begin()), &*it_}; - } - auto operator->() const - { - return &*it_; - } - IteratorWrapper operator[](std::size_t offset) const - { - return IteratorWrapper{owner_, it_[offset]}; - } - bool operator<(IteratorWrapper const& other) const - { - return it_ < other.it_; - } - bool operator>(IteratorWrapper const& other) const - { - return it_ > other.it_; - } - bool operator<=(IteratorWrapper const& other) const - { - return it_ <= other.it_; - } - bool operator>=(IteratorWrapper const& other) const - { - return it_ >= other.it_; - } - bool operator==(IteratorWrapper const& other) const - { - return it_ == other.it_; - } - WrappedIterator getWrapped() const - { - return it_; - } - - private: - ObservedContainer* owner_; - WrappedIterator it_; - }; - }; - - template - class ObservedContainer : public ModifiableObserved - { - public: - friend class ContainerWrapUtility::ReferenceWrapper; - friend class ContainerWrapUtility::PointerWrapper; - - using value_type = typename ContainerT::value_type; - using allocator_type = typename ContainerT::allocator_type; - using size_type = typename ContainerT::size_type; - using difference_type = typename ContainerT::difference_type; - using reference = ContainerWrapUtility::ReferenceWrapper; - using const_reference = typename ContainerT::const_reference; - using pointer = ContainerWrapUtility::PointerWrapper; - using const_pointer = typename ContainerT::const_pointer; - - using iterator = ContainerWrapUtility::IteratorWrapper; - using const_iterator = typename ContainerT::const_iterator; - using reverse_iterator = - ContainerWrapUtility::IteratorWrapper; - using const_reverse_iterator = typename ContainerT::const_reverse_iterator; - - using ModifiableObserved::contained_; - using ModifiableObserved::update; - - public: - ObservedContainer() - : ModifiableObserved{} - , rangeContext_{0} - , afterEffectId_{registerAfterEffect()} - {} - template - explicit ObservedContainer(T&& t) - : ModifiableObserved{std::forward(t)} - , rangeContext_{static_cast(contained_.size())} - , afterEffectId_{registerAfterEffect()} - {} - explicit ObservedContainer(RangeEventContext&& rangeContext) - : ModifiableObserved{} - , rangeContext_{std::move(rangeContext)} - , afterEffectId_{registerAfterEffect()} - {} - template - ObservedContainer(T&& t, RangeEventContext&& rangeContext) - : ModifiableObserved{std::forward(t)} - , rangeContext_{std::move(rangeContext)} - , afterEffectId_{registerAfterEffect()} - {} - - ObservedContainer(const ObservedContainer&) = delete; - ObservedContainer(ObservedContainer&&) = default; - ObservedContainer& operator=(const ObservedContainer&) = delete; - ObservedContainer& operator=(ObservedContainer&&) = default; - ~ObservedContainer() = default; - - constexpr auto map(auto&& function) const; - constexpr auto map(auto&& function); - - template - ObservedContainer& operator=(T&& t) - { - contained_ = std::forward(t); - rangeContext_.reset(static_cast(contained_.size()), true); - update(); - return *this; - } - void assign(size_type count, const value_type& value) - { - contained_.assign(count, value); - rangeContext_.reset(static_cast(contained_.size()), true); - update(); - } - template - void assign(Iterator first, Iterator last) - { - contained_.assign(first, last); - rangeContext_.reset(static_cast(contained_.size()), true); - update(); - } - void assign(std::initializer_list ilist) - { - contained_.assign(ilist); - rangeContext_.reset(static_cast(contained_.size()), true); - update(); - } - - // Element access - reference front() - { - return reference{this, 0, contained_.front()}; - } - const_reference front() const - { - return contained_.front(); - } - reference back() - { - return reference{this, contained_.size() - 1, contained_.back()}; - } - const_reference back() const - { - return contained_.back(); - } - pointer data() noexcept - { - return pointer{this, 0, contained_.data()}; - } - const_pointer data() const noexcept - { - return contained_.data(); - } - reference at(size_type pos) - { - return reference{this, pos, contained_.at(pos)}; - } - const_reference at(size_type pos) const - { - return contained_.at(pos); - } - reference operator[](size_type pos) - { - return reference{this, pos, contained_[pos]}; - } - const_reference operator[](size_type pos) const - { - return contained_[pos]; - } - - // Iterators - iterator begin() noexcept - { - return iterator{this, contained_.begin()}; - } - const_iterator begin() const noexcept - { - return contained_.begin(); - } - iterator end() noexcept - { - return iterator{this, contained_.end()}; - } - const_iterator end() const noexcept - { - return contained_.end(); - } - const_iterator cbegin() const noexcept - { - return contained_.cbegin(); - } - const_iterator cend() const noexcept - { - return contained_.cend(); - } - reverse_iterator rbegin() noexcept - { - return reverse_iterator{this, contained_.rbegin()}; - } - const_reverse_iterator rbegin() const noexcept - { - return contained_.rbegin(); - } - reverse_iterator rend() noexcept - { - return reverse_iterator{this, contained_.rend()}; - } - const_reverse_iterator rend() const noexcept - { - return contained_.rend(); - } - const_reverse_iterator crbegin() const noexcept - { - return contained_.crbegin(); - } - const_reverse_iterator crend() const noexcept - { - return contained_.crend(); - } - - // Capacity - bool empty() const noexcept - { - return contained_.empty(); - } - std::size_t size() const noexcept - { - return contained_.size(); - } - template - Detail::PickFirst_t().max_size())> max_size() const noexcept - { - return contained_.max_size(); - } - template - Detail::PickFirst_t().reserve(std::declval()))> - reserve(size_type capacity) - { - return contained_.reserve(capacity); - } - template - Detail::PickFirst_t().capacity())> capacity() const noexcept - { - return contained_.capacity(); - } - template - Detail::PickFirst_t().shrink_to_fit())> shrink_to_fit() - { - return contained_.shrink_to_fit(); - } - - // Modifiers - void clear() - { - contained_.clear(); - rangeContext_.reset(0, true); - update(); - } - template - Detail::PickFirst_t< - std::pair, - decltype(std::declval().insert(std::declval()))> - insert(const value_type& value) - { - const auto result = contained_.insert(value); - rangeContext_.performFullRangeUpdate(); - update(); - globalEventContext.executeActiveEventsImmediately(); - return result; - } - template - Detail::PickFirst_t< - std::pair, - decltype(std::declval().insert(std::declval()))> - insert(value_type&& value) - { - const auto result = contained_.insert(std::move(value)); - rangeContext_.performFullRangeUpdate(); - update(); - globalEventContext.executeActiveEventsImmediately(); - return result; - } - iterator insert(iterator pos, const value_type& value) - { - return insert(pos.getWrapped(), value); - } - iterator insert(const_iterator pos, const value_type& value) - { - const auto distance = pos - cbegin(); - auto it = contained_.insert(pos, value); - insertRangeChecked(distance, distance, RangeStateType::Insert); - return iterator{this, it}; - } - iterator insert(iterator pos, value_type&& value) - { - return insert(pos.getWrapped(), std::move(value)); - } - iterator insert(const_iterator pos, value_type&& value) - { - const auto distance = pos - cbegin(); - auto it = contained_.insert(pos, std::move(value)); - insertRangeChecked(distance, distance, RangeStateType::Insert); - return iterator{this, it}; - } - iterator insert(iterator pos, size_type count, const value_type& value) - { - return insert(pos.getWrapped(), count, value); - } - iterator insert(const_iterator pos, size_type count, const value_type& value) - { - const auto distance = pos - cbegin(); - auto it = contained_.insert(pos, count, value); - insertRangeChecked(distance, distance + count, RangeStateType::Insert); - return iterator{this, it}; - } - template - iterator insert(iterator pos, Iterator first, Iterator last) - { - return insert(pos.getWrapped(), first, last); - } - template - iterator insert(const_iterator pos, Iterator first, Iterator last) - { - const auto distance = pos - cbegin(); - auto it = contained_.insert(pos, first, last); - insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Insert); - return iterator{this, it}; - } - iterator insert(iterator pos, std::initializer_list ilist) - { - return insert(pos.getWrapped(), ilist); - } - iterator insert(const_iterator pos, std::initializer_list ilist) - { - const auto distance = pos - cbegin(); - auto it = contained_.insert(pos, ilist); - insertRangeChecked(distance, distance + ilist.size(), RangeStateType::Insert); - return iterator{this, it}; - } - template - iterator emplace(const_iterator pos, Args&&... args) - { - const auto distance = pos - cbegin(); - auto it = contained_.emplace(pos, std::forward(args)...); - insertRangeChecked(distance, distance + sizeof...(Args), RangeStateType::Insert); - return iterator{this, it}; - } - iterator erase(iterator pos) - { - // TODO: move item to delete to end and then pop back? or is vector erase clever enough? - const auto distance = pos - begin(); - auto it = contained_.erase(pos.getWrapped()); - insertRangeChecked(distance, distance, RangeStateType::Erase); - return iterator{this, it}; - } - iterator erase(const_iterator pos) - { - const auto distance = pos - cbegin(); - auto it = contained_.erase(pos); - insertRangeChecked(distance, distance, RangeStateType::Erase); - return iterator{this, it}; - } - iterator erase(iterator first, iterator last) - { - const auto distance = first - begin(); - auto it = contained_.erase(first.getWrapped(), last.getWrapped()); - insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Erase); - return iterator{this, it}; - } - iterator erase(const_iterator first, const_iterator last) - { - const auto distance = first - cbegin(); - auto it = contained_.erase(first, last); - insertRangeChecked(distance, distance + std::distance(first, last), RangeStateType::Erase); - return iterator{this, it}; - } - template - Detail::PickFirst_t().push_back(std::declval()))> - push_back(const value_type& value) - { - contained_.push_back(value); - insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); - } - template - Detail::PickFirst_t().push_back(std::declval()))> - push_back(value_type&& value) - { - contained_.push_back(std::move(value)); - insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); - } - template - Detail::PickFirst_t().push_front(std::declval()))> - push_front(const value_type& value) - { - contained_.push_front(value); - insertRangeChecked(0, 0, RangeStateType::Insert); - } - template - Detail::PickFirst_t().push_front(std::declval()))> - push_front(value_type&& value) - { - contained_.push_front(std::move(value)); - insertRangeChecked(0, 0, RangeStateType::Insert); - } - template - void emplace_back(Args&&... args) - { - contained_.emplace_back(std::forward(args)...); - insertRangeChecked(size() - 1, size() - 1, RangeStateType::Insert); - } - template - Detail::PickFirst_t().emplace_front())> emplace_front(Args&&... args) - { - contained_.emplace_front(std::forward(args)...); - insertRangeChecked(0, 0, RangeStateType::Insert); - } - void pop_back() - { - contained_.pop_back(); - insertRangeChecked(size(), size(), RangeStateType::Erase); - } - template - Detail::PickFirst_t().pop_front())> pop_front() - { - contained_.pop_front(); - insertRangeChecked(0, 0, RangeStateType::Erase); - } - template - Detail::PickFirst_t().resize(std::declval()))> - resize(size_type count) - { - const auto sizeBefore = contained_.size(); - contained_.resize(count); - if (sizeBefore < count) - insertRangeChecked(sizeBefore, count, RangeStateType::Insert); - else - insertRangeChecked(count, sizeBefore, RangeStateType::Erase); - } - template - Detail::PickFirst_t< - void, - decltype(std::declval().resize(std::declval(), std::declval()))> - resize(size_type count, value_type const& fillValue) - { - const auto sizeBefore = contained_.size(); - contained_.resize(count, fillValue); - if (sizeBefore < count) - insertRangeChecked(sizeBefore, count, RangeStateType::Insert); - else - insertRangeChecked(count, sizeBefore, RangeStateType::Erase); - } - void swap(ContainerT& other) - { - contained_.swap(other); - rangeContext_.reset(contained_.size(), true); - update(); - } - - // Other - ContainerT& value() - { - return contained_; - } - ContainerT const& value() const - { - return contained_; - } - RangeEventContext& rangeContext() - { - return rangeContext_; - } - RangeEventContext const& rangeContext() const - { - return rangeContext_; - } - - protected: - void update(bool force = false) const override - { - if (force) - rangeContext_.reset(static_cast(contained_.size()), true); - ObservedBase::update(force); - } - - protected: - void insertRangeChecked(std::size_t low, std::size_t high, RangeStateType type) - { - std::function doInsert; - doInsert = [&](int retries) { - const auto result = rangeContext_.insertModificationRange(contained_.size(), low, high, type); - if (result == RangeEventContext::InsertResult::Final) - { - update(); - globalEventContext.executeActiveEventsImmediately(); - } - else if (result == RangeEventContext::InsertResult::Retry) - { - if (retries < 3) - doInsert(retries + 1); - else - { - rangeContext_.reset(static_cast(contained_.size()), true); - update(); - globalEventContext.executeActiveEventsImmediately(); - return; - } - } - else - update(); - }; - - doInsert(0); - } - - auto registerAfterEffect() - { - return globalEventContext.registerAfterEffect(Event{[this](EventContext::EventIdType) { - rangeContext_.reset(static_cast(contained_.size()), true); - return true; - }}); - } - - private: - mutable RangeEventContext rangeContext_; - mutable EventContext::EventIdType afterEffectId_; - }; - - template - class Observed : public ModifiableObserved - { - public: - using ModifiableObserved::ModifiableObserved; - using ModifiableObserved::operator=; - using ModifiableObserved::operator->; - - Observed& operator=(T const& contained) - { - ModifiableObserved::operator=(contained); - return *this; - } - Observed& operator=(T&& contained) - { - ModifiableObserved::operator=(std::move(contained)); - return *this; - } - }; - template - class Observed> : public ObservedContainer> - { - public: - using ObservedContainer>::ObservedContainer; - using ObservedContainer>::operator=; - using ObservedContainer>::operator->; - static constexpr auto isRandomAccess = true; - - Observed>& operator=(std::vector const& contained) - { - ObservedContainer>::operator=(contained); - return *this; - } - Observed>& operator=(std::vector&& contained) - { - ObservedContainer>::operator=(std::move(contained)); - return *this; - } - }; - template - class Observed> : public ObservedContainer> - { - public: - using ObservedContainer>::ObservedContainer; - using ObservedContainer>::operator=; - using ObservedContainer>::operator->; - static constexpr auto isRandomAccess = true; - - Observed>& operator=(std::deque const& contained) - { - ObservedContainer>::operator=(contained); - return *this; - } - Observed>& operator=(std::deque&& contained) - { - ObservedContainer>::operator=(std::move(contained)); - return *this; - } - }; - template - class Observed> : public ObservedContainer> - { - public: - using ObservedContainer>::ObservedContainer; - using ObservedContainer>::operator=; - using ObservedContainer>::operator->; - static constexpr auto isRandomAccess = true; - - Observed>& operator=(std::basic_string const& contained) - { - ObservedContainer>::operator=(contained); - return *this; - } - Observed>& operator=(std::basic_string&& contained) - { - ObservedContainer>::operator=(std::move(contained)); - return *this; - } - - Observed>& erase(std::size_t index = 0, std::size_t count = std::string::npos) - { - const auto sizeBefore = this->contained_.size(); - this->contained_.erase(index, count); - this->insertRangeChecked(index, sizeBefore, RangeStateType::Erase); - return *this; - } - }; - template - class Observed> : public ObservedContainer> - { - public: - using ObservedContainer>::ObservedContainer; - using ObservedContainer>::operator=; - using ObservedContainer>::operator->; - static constexpr auto isRandomAccess = false; - - public: - Observed() - : ObservedContainer>{RangeEventContext{0, true}} - {} - template > - explicit Observed(T&& t) - : ObservedContainer>{ - std::forward(t), - RangeEventContext{static_cast(t.size()), true}} - {} - - Observed>& operator=(std::set const& contained) - { - ObservedContainer>::operator=(contained); - return *this; - } - Observed>& operator=(std::set&& contained) - { - ObservedContainer>::operator=(std::move(contained)); - return *this; - } - }; - template - class Observed> : public ObservedContainer> - { - public: - using ObservedContainer>::ObservedContainer; - using ObservedContainer>::operator=; - using ObservedContainer>::operator->; - static constexpr auto isRandomAccess = false; - - public: - Observed() - : ObservedContainer>{RangeEventContext{0, true}} - {} - template > - explicit Observed(T&& t) - : ObservedContainer>{ - std::forward(t), - RangeEventContext{static_cast(t.size()), true}} - {} - - Observed>& operator=(std::list const& contained) - { - ObservedContainer>::operator=(contained); - return *this; - } - Observed>& operator=(std::list&& contained) - { - ObservedContainer>::operator=(std::move(contained)); - return *this; - } - }; - - template <> - class Observed : public ObservedBase - { - void modify() const - { - update(); - }; - }; - - namespace Detail - { - template - struct IsObserved - { - static constexpr bool value = false; - }; - - template - struct IsObserved> - { - static constexpr bool value = true; - }; - } - - template - requires Incrementable - inline ModifiableObserved& operator++(ModifiableObserved& observedValue) - { - ++observedValue.value(); - observedValue.update(); - return observedValue; - } - template - requires Incrementable - inline T operator++(ModifiableObserved& observedValue, int) - { - auto tmp = observedValue.value(); - ++observedValue.value(); - observedValue.update(); - return tmp; - } - - template - inline auto operator--(ModifiableObserved& observedValue) - -> ModifiableObserved())>>& - { - --observedValue.value(); - observedValue.update(); - return observedValue; - } - template - inline auto - operator--(ModifiableObserved& observedValue, int) -> Detail::PickFirst_t()--)> - { - auto tmp = observedValue.value(); - --observedValue.value(); - observedValue.update(); - return tmp; - } - - template - concept IsObserved = Detail::IsObserved>::value; - - namespace Detail - { - template - struct CopyableObservedWrap // minimal wrapper to make Observed copiable - { - public: - explicit constexpr CopyableObservedWrap(Observed const& observed) - : observed_{&observed} - {} - - inline T value() const - { - return observed_->value(); - } - - inline void attachEvent(auto eventId) const - { - observed_->attachEvent(eventId); - } - - inline void detachEvent(auto eventId) const - { - observed_->detachEvent(eventId); - } - - private: - Observed const* observed_; - }; - } -} \ No newline at end of file +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/observed_value_combinator.hpp b/nui/include/nui/frontend/event_system/observed_value_combinator.hpp index 1205e7cb..cc376c90 100644 --- a/nui/include/nui/frontend/event_system/observed_value_combinator.hpp +++ b/nui/include/nui/frontend/event_system/observed_value_combinator.hpp @@ -1,149 +1,5 @@ -#pragma once - -#include -#include -#include - -#include - -namespace Nui -{ - template - class ObservedValueCombinatorBase - { - public: - explicit constexpr ObservedValueCombinatorBase(ObservedValues const&... observedValues) - : observedValues_{observedValues...} - {} - explicit constexpr ObservedValueCombinatorBase(std::tuple observedValues) - : observedValues_{std::move(observedValues)} - {} - - constexpr void attachEvent(auto eventId) const - { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.attachEvent(eventId); - }); - } - - constexpr void attachOneshotEvent(auto eventId) const - { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.attachOneshotEvent(eventId); - }); - } - - constexpr void detachEvent(auto eventId) const - { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.detachEvent(eventId); - }); - } - - std::tuple const& observedValues() & - { - return observedValues_; - } - - std::tuple&& observedValues() && - { - return std::move(const_cast&>(observedValues_)); - } - - protected: - const std::tuple observedValues_; - }; - template - ObservedValueCombinatorBase(std::tuple) -> ObservedValueCombinatorBase; - template - ObservedValueCombinatorBase(ObservedValues const&...) -> ObservedValueCombinatorBase; - - template - class ObservedValueCombinator; - - template - class ObservedValueCombinatorWithGenerator : public ObservedValueCombinatorBase - { - public: - constexpr ObservedValueCombinatorWithGenerator( - std::tuple observedValues, - RendererType generator) - : ObservedValueCombinatorBase{std::move(observedValues)} - , generator_{std::move(generator)} - {} - - ObservedValueCombinator split() && - { - return ObservedValueCombinator{std::move(this->observedValues_)}; - } - - constexpr auto value() const - { - return generator_(); - } - - RendererType generator() const& - { - return generator_; - } - RendererType generator() && - { - return std::move(generator_); - } - - protected: - const RendererType generator_; - }; - - template - class ObservedValueCombinatorWithPropertyGenerator - : public ObservedValueCombinatorWithGenerator - { - public: - using ObservedValueCombinatorWithGenerator:: - ObservedValueCombinatorWithGenerator; - ObservedValueCombinatorWithPropertyGenerator( - ObservedValueCombinatorWithGenerator&& other) - : ObservedValueCombinatorWithGenerator{std::move(other)} - {} - - using ObservedValueCombinatorWithGenerator::split; - using ObservedValueCombinatorWithGenerator::value; - using ObservedValueCombinatorWithGenerator::generator; - }; - - template - class ObservedValueCombinator : public ObservedValueCombinatorBase - { - public: - using ObservedValueCombinatorBase::ObservedValueCombinatorBase; - using ObservedValueCombinatorBase::observedValues_; - - template - requires std::invocable - constexpr ObservedValueCombinatorWithGenerator - generate(RendererType&& generator) - { - return ObservedValueCombinatorWithGenerator{ - observedValues_, std::forward(generator)}; - } - - template - requires std::invocable - constexpr ObservedValueCombinatorWithPropertyGenerator - generateProperty(RendererType&& generator) - { - return ObservedValueCombinatorWithPropertyGenerator{ - observedValues_, std::forward(generator)}; - } - }; - template - ObservedValueCombinator(ObservedValues const&...) -> ObservedValueCombinator; - - template - requires(Detail::IsObserved::value && ...) - ObservedValueCombinator observe(ObservedValues const&... observedValues) - { - return ObservedValueCombinator(observedValues...); - } -} \ No newline at end of file + +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/range.hpp b/nui/include/nui/frontend/event_system/range.hpp index 4759225c..abd31581 100644 --- a/nui/include/nui/frontend/event_system/range.hpp +++ b/nui/include/nui/frontend/event_system/range.hpp @@ -1,178 +1,4 @@ -#pragma once - -#include -#include -#include - -#include -#include - -#ifdef NUI_HAS_STD_RANGES -# include -#endif - -namespace Nui -{ - template - class ObservedRange - { - public: - using ObservedType = ObservedValue; - - static constexpr bool isRandomAccess = ObservedType::isRandomAccess; - - explicit constexpr ObservedRange(ObservedValue& observedValues) - : observedValue_{observedValues} - {} - - ObservedValue const& underlying() const - { - return observedValue_; - } - ObservedValue& underlying() - requires(!std::is_const_v) - { - return observedValue_; - } - - private: - ObservedValue& observedValue_; - }; - - template - class UnoptimizedRange - { - public: - UnoptimizedRange(ObservedValueCombinator&& observedValues, CopyableRangeLike&& rangeLike) - : observedValues_{std::move(observedValues)} - , rangeLike_{std::move(rangeLike)} - {} - UnoptimizedRange(CopyableRangeLike&& rangeLike) - requires(sizeof...(ObservedValues) == 0) - : observedValues_{} - , rangeLike_{std::move(rangeLike)} - {} - - auto begin() const - { - return rangeLike_.begin(); - } - auto end() const - { - return rangeLike_.end(); - } - - ObservedValueCombinator const& underlying() const - { - return observedValues_; - } - ObservedValueCombinator& underlying() - { - return observedValues_; - } - - private: - ObservedValueCombinator observedValues_; - CopyableRangeLike rangeLike_; - }; - - template - requires(IsObserved) - ObservedRange range(ObservedValue& observedValues) - { - return ObservedRange{observedValues}; - } - - template - requires(IsObserved) - ObservedRange range(ObservedValue const& observedValues) - { - return ObservedRange{observedValues}; - } - - template - UnoptimizedRange, Observed...> - range(ContainerT const& container, Observed const&... observed) - { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange, Observed...> - range(ContainerT& container, Observed const&... observed) - { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange, Observed...> - range(ContainerT const& container, Observed&... observed) - { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange, Observed...> range(ContainerT& container, Observed&... observed) - { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange> range(ContainerT& container) - { - return UnoptimizedRange>{ - IteratorAccessor{container}, - }; - } - - template - UnoptimizedRange> range(ContainerT const& container) - { - return UnoptimizedRange>{ - IteratorAccessor{container}, - }; - } - -#ifdef NUI_HAS_STD_RANGES - template - UnoptimizedRange>, Observed...> - range(T const& container, Observed const&... observed) - { - return UnoptimizedRange>, Observed...>{ - ObservedValueCombinator{observed...}, - std::ranges::subrange>{ - std::ranges::begin(container), std::ranges::end(container)}, - }; - } -#endif - - template - constexpr auto ObservedContainer::map(auto&& function) const - { - return std::pair>, std::decay_t>{ - ObservedRange>{static_cast const&>(*this)}, - std::forward>(function), - }; - } - - template - constexpr auto ObservedContainer::map(auto&& function) - { - return std::pair>, std::decay_t>{ - ObservedRange>{static_cast&>(*this)}, - std::forward>(function), - }; - } -} \ No newline at end of file +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/include/nui/frontend/event_system/range_event_context.hpp b/nui/include/nui/frontend/event_system/range_event_context.hpp index 76cdca5f..b9aefdd1 100644 --- a/nui/include/nui/frontend/event_system/range_event_context.hpp +++ b/nui/include/nui/frontend/event_system/range_event_context.hpp @@ -1,295 +1,4 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -namespace Nui -{ - enum RangeStateType - { - Keep = 0b0001, - Modify = 0b0010, - Insert = 0b0100, - Erase = 0b1000, - Extended = 0b0001'0000 - }; - - namespace Detail - { - template - class RangeStateInterval; - - template - std::vector> cutIntervals( - RangeStateInterval const& k, - RangeStateInterval const& m); - - template - class RangeStateInterval - { - public: - using value_type = ValueType; - using interval_kind = IntervalKind; - - public: - RangeStateInterval(value_type low, value_type high, RangeStateType type) - : low_{low} - , high_{high} - , type_{type} - {} - void reset(value_type low, value_type high, RangeStateType type) - { - low_ = low; - high_ = high; - type_ = type; - } - friend bool operator==(RangeStateInterval const& lhs, RangeStateInterval const& rhs) - { - return lhs.start_ == rhs.start_ && lhs.end_ == rhs.end_ && lhs.type_ == rhs.type_; - } - friend bool operator!=(RangeStateInterval const& lhs, RangeStateInterval const& rhs) - { - return !(lhs == rhs); - } - value_type low() const - { - return low_; - } - value_type high() const - { - return high_; - } - bool overlaps(value_type l, value_type h) const - { - return low_ <= h && l <= high_; - } - bool overlaps_exclusive(value_type l, value_type h) const - { - return low_ < h && l < high_; - } - bool overlaps(RangeStateInterval const& other) const - { - return overlaps(other.low_, other.high_); - } - bool overlapsOrIsAdjacent(RangeStateInterval const& other) const - { - return low_ <= other.high_ + 1 && other.low_ - 1 <= high_; - } - bool overlaps_exclusive(RangeStateInterval const& other) const - { - return overlaps_exclusive(other.low_, other.high_); - } - bool within(value_type value) const - { - return interval_kind::within(low_, high_, value); - } - bool within(RangeStateInterval const& other) const - { - return low_ <= other.low_ && high_ >= other.high_; - } - value_type operator-(RangeStateInterval const& other) const - { - if (overlaps(other)) - return 0; - if (high_ < other.low_) - return other.low_ - high_; - else - return low_ - other.high_; - } - value_type size() const - { - return high_ - low_; - } - std::vector join(RangeStateInterval const& other) const - { - auto typeWithoutExtension = static_cast(other.type_ & ~RangeStateType::Extended); - long extensionFix = other.type_ & RangeStateType::Extended ? 1 : 0; - switch (typeWithoutExtension | type_) - { - case (RangeStateType::Modify | RangeStateType::Modify): - { - return { - {std::min(low_, other.low_ + extensionFix), - std::max(high_, other.high_ - extensionFix), - RangeStateType::Modify}}; - } - case (RangeStateType::Keep | RangeStateType::Modify): - { - // Modifications cut out of the keep part. - return cutIntervals( - *this, {other.low_ + extensionFix, other.high_ - extensionFix, typeWithoutExtension}); - } - default: - { - throw std::runtime_error("Invalid insertion case"); - } - } - } - RangeStateType type() const noexcept - { - return type_; - } - - private: - value_type low_; - value_type high_; - RangeStateType type_; - }; - - // stream iterator for intervals - template - std::ostream& operator<<(std::ostream& os, RangeStateInterval const& interval) - { - os << "[" << interval.low() << ", " << interval.high() << "]"; - switch (interval.type()) - { - case RangeStateType::Keep: - os << "k"; - break; - case RangeStateType::Modify: - os << "m"; - break; - case RangeStateType::Insert: - os << "i"; - break; - default: - os << "?"; - break; - } - return os; - } - - template - std::vector> cutIntervals( - RangeStateInterval const& k, - RangeStateInterval const& m) - { - std::vector> result; - if (k.low() <= m.low() - 1) - result.emplace_back(k.low(), m.low() - 1, RangeStateType::Keep); - result.emplace_back(m.low(), m.high(), RangeStateType::Modify); - if (k.high() >= m.high() + 1) - result.emplace_back(m.high() + 1, k.high(), RangeStateType::Keep); - return result; - } - } - - class RangeEventContext - { - public: - explicit RangeEventContext(long dataSize) - : modificationRanges_{} - , fullRangeUpdate_{true} - , disableOptimizations_{false} - { - reset(dataSize, true); - } - RangeEventContext(long dataSize, bool disableOptimizations) - : modificationRanges_{} - , fullRangeUpdate_{true} - , disableOptimizations_{disableOptimizations} - { - reset(dataSize, true); - } - enum class InsertResult - { - Final, // Final, cannot accept further updates, must update immediately - Accepted, // Accepted, update can be deferred. - Retry // Must update immediately and reissue this. - }; - void performFullRangeUpdate() - { - fullRangeUpdate_ = true; - } - InsertResult insertModificationRange(long elementCount, long low, long high, RangeStateType type) - { - if (disableOptimizations_) - { - fullRangeUpdate_ = true; - return InsertResult::Final; - } - if (type == RangeStateType::Erase) - { - // TODO: optimize erase like insert. - reset(elementCount, true); - return InsertResult::Final; - } - else if (type == RangeStateType::Insert) - { - if (modificationRanges_.size() > 1) - return InsertResult::Retry; - if (!insertInterval_) - { - insertInterval_ = {low, high, type}; - return InsertResult::Accepted; - } - else - { - if (insertInterval_->overlapsOrIsAdjacent({low, high, type})) - { - auto lowmin = std::min(low, insertInterval_->low()); - insertInterval_->reset(lowmin, lowmin + insertInterval_->size() + (high - low + 1), type); - return InsertResult::Accepted; - } - else - { - if (high < insertInterval_->low()) - { - const auto size = high - low + 1; - insertInterval_->reset( - insertInterval_->low() + size, insertInterval_->high() + size, insertInterval_->type()); - } - return InsertResult::Retry; - } - } - } - if (insertInterval_) - return InsertResult::Retry; - - auto iter = modificationRanges_.insert_overlap( - {low - 1, high + 1, static_cast(type | RangeStateType::Extended)}, false, true); - return InsertResult::Accepted; - } - InsertResult - insertModificationRange(std::size_t elementCount, std::size_t low, std::size_t high, RangeStateType type) - { - return insertModificationRange( - static_cast(elementCount), static_cast(low), static_cast(high), type); - } - void reset(long dataSize, bool requireFullRangeUpdate) - { - modificationRanges_.clear(); - if (dataSize > 0) - modificationRanges_.insert({0l, dataSize - 1, RangeStateType::Keep}); - insertInterval_ = std::nullopt; - fullRangeUpdate_ = requireFullRangeUpdate; - } - bool isFullRangeUpdate() const noexcept - { - return fullRangeUpdate_; - } - std::optional> const& insertInterval() const - { - return insertInterval_; - } - auto begin() const - { - return modificationRanges_.begin(); - } - auto end() const - { - return modificationRanges_.end(); - } - - private: - lib_interval_tree::interval_tree> modificationRanges_; - std::optional> insertInterval_; - bool fullRangeUpdate_; - bool disableOptimizations_; - }; -} \ No newline at end of file +#pragma once + +// Content was moved here +#include \ No newline at end of file diff --git a/nui/src/nui/CMakeLists.txt b/nui/src/nui/CMakeLists.txt index 25753d0c..3287c133 100644 --- a/nui/src/nui/CMakeLists.txt +++ b/nui/src/nui/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(event_system) if (EMSCRIPTEN) add_subdirectory(frontend) else() diff --git a/nui/src/nui/backend/CMakeLists.txt b/nui/src/nui/backend/CMakeLists.txt index 09538469..ce7e9555 100644 --- a/nui/src/nui/backend/CMakeLists.txt +++ b/nui/src/nui/backend/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(nui-backend rpc_addons/timer.cpp rpc_addons/screen.cpp rpc_addons/environment_variables.cpp + ../event_system/event_context.cpp ) add_library(Nui::backend ALIAS nui-backend) target_include_directories( diff --git a/nui/src/nui/event_system/CMakeLists.txt b/nui/src/nui/event_system/CMakeLists.txt new file mode 100644 index 00000000..368b7006 --- /dev/null +++ b/nui/src/nui/event_system/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(nui-events STATIC event_context.cpp) +add_library(Nui::events ALIAS nui-events) +target_include_directories( + nui-events + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/../../../include" +) +target_link_libraries( + nui-events + PUBLIC + boost_preprocessor + interval-tree +) +nui_set_project_warnings(nui-events) +nui_set_target_output_directories(nui-events) +target_compile_features(nui-events PUBLIC cxx_std_20) +set_target_properties(nui-events PROPERTIES CXX_STANDARD_REQUIRED OFF) +set_target_properties(nui-events PROPERTIES CXX_EXTENSIONS OFF) diff --git a/nui/src/nui/frontend/event_system/event_context.cpp b/nui/src/nui/event_system/event_context.cpp similarity index 53% rename from nui/src/nui/frontend/event_system/event_context.cpp rename to nui/src/nui/event_system/event_context.cpp index 043d3b43..2656eef6 100644 --- a/nui/src/nui/frontend/event_system/event_context.cpp +++ b/nui/src/nui/event_system/event_context.cpp @@ -1,4 +1,4 @@ -#include +#include namespace Nui { diff --git a/nui/src/nui/frontend/CMakeLists.txt b/nui/src/nui/frontend/CMakeLists.txt index 749b6179..a78f30aa 100644 --- a/nui/src/nui/frontend/CMakeLists.txt +++ b/nui/src/nui/frontend/CMakeLists.txt @@ -9,6 +9,7 @@ target_include_directories( target_link_libraries( nui-frontend PUBLIC + nui-events boost_preprocessor libcpppre mplex diff --git a/nui/src/nui/frontend/frontend_sources.cmake b/nui/src/nui/frontend/frontend_sources.cmake index 476c6728..37862d2b 100644 --- a/nui/src/nui/frontend/frontend_sources.cmake +++ b/nui/src/nui/frontend/frontend_sources.cmake @@ -5,7 +5,6 @@ set(NUI_FRONTEND_SOURCES_RELATIVE attributes/impl/attribute.cpp components/dialog.cpp dom/dom.cpp - event_system/event_context.cpp extensions/make_resizeable.cpp filesystem/file_dialog.cpp filesystem/file.cpp diff --git a/nui/test/nui/CMakeLists.txt b/nui/test/nui/CMakeLists.txt index f2156eba..1aad13a9 100644 --- a/nui/test/nui/CMakeLists.txt +++ b/nui/test/nui/CMakeLists.txt @@ -12,6 +12,7 @@ target_link_libraries(nui-frontend-mocked PUBLIC mplex interval-tree Boost::boost + nui-events ) target_compile_definitions(nui-frontend-mocked PRIVATE __EMSCRIPTEN__) target_compile_features(nui-frontend-mocked PUBLIC cxx_std_20) diff --git a/nui/test/nui/engine/document.cpp b/nui/test/nui/engine/document.cpp index 710085eb..7a306647 100644 --- a/nui/test/nui/engine/document.cpp +++ b/nui/test/nui/engine/document.cpp @@ -7,6 +7,7 @@ #include "global_object.hpp" #include +#include namespace Nui::Tests::Engine { diff --git a/nui/test/nui/test_events.hpp b/nui/test/nui/test_events.hpp new file mode 100644 index 00000000..d3e471d4 --- /dev/null +++ b/nui/test/nui/test_events.hpp @@ -0,0 +1,270 @@ +#pragma once + +#include + +#include "common_test_fixture.hpp" +#include "engine/global_object.hpp" +#include "engine/document.hpp" +#include "engine/object.hpp" + +#include +#include +#include + +namespace Nui::Tests +{ + using namespace Engine; + using namespace std::string_literals; + + class TestEvents : public CommonTestFixture + {}; + + TEST_F(TestEvents, ListenedEventIsExecuted) + { + Observed obs; + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + obs = 42; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, ListenedEventIsNotExecutedWhenValueDoesNotChangeAndEventsAreNotProcessed) + { + Observed obs; + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + EXPECT_EQ(calledWith, 77); + } + + TEST_F(TestEvents, ListenedEventIsNotExecutedWhenEventsAreNotProcessed) + { + Observed obs; + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + obs = 42; + + EXPECT_EQ(calledWith, 77); + } + + TEST_F(TestEvents, ListenedEventIsNotExecutedWhenValueDoesNotChange) + { + Observed obs{42}; + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 77); + } + + TEST_F(TestEvents, ListenedSharedObservedEventIsNotExecutedWhenEventsAreNotProcessed) + { + auto obs = std::make_shared>(40); + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + *obs = 42; + + EXPECT_EQ(calledWith, 77); + } + + TEST_F(TestEvents, ListenedSharedObservedEventIsNotExecutedWhenValueDoesNotChange) + { + auto obs = std::make_shared>(40); + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 77); + } + + TEST_F(TestEvents, CanUseSharedObservedWithListen) + { + auto obs = std::make_shared>(); + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + *obs = 42; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, SharedObservedMayBeDestroyed) + { + auto obs = std::make_shared>(); + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + obs.reset(); + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 77); + } + + TEST_F(TestEvents, CanUseCustomEventContext) + { + EventContext eventContext; + + Observed obs{&eventContext}; + + int calledWith = 77; + listen(eventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + obs = 42; + eventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, CanUseListenerReturningVoid) + { + EventContext eventContext; + + Observed obs{&eventContext}; + + int calledWith = 77; + listen(eventContext, obs, [&calledWith](int const& value) -> void { + calledWith = value; + }); + + obs = 42; + eventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, CanUseListenerReturningVoidWithSharedObserved) + { + EventContext eventContext; + + std::shared_ptr> obs = std::make_shared>(&eventContext); + + int calledWith = 77; + listen(eventContext, obs, [&calledWith](int const& value) -> void { + calledWith = value; + }); + + *obs = 42; + eventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, ListenEventIsNotRemovedWhenReturningTrue) + { + EventContext eventContext; + + std::shared_ptr> obs = std::make_shared>(&eventContext); + + int calledWith = 77; + listen(eventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + *obs = 42; + eventContext.executeActiveEventsImmediately(); + EXPECT_EQ(calledWith, 42); + + *obs = 45; + eventContext.executeActiveEventsImmediately(); + EXPECT_EQ(calledWith, 45); + } + + TEST_F(TestEvents, ListenEventIsRemovedWhenReturningFalse) + { + EventContext eventContext; + + std::shared_ptr> obs = std::make_shared>(&eventContext); + + int calledWith = 77; + listen(eventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return false; + }); + + *obs = 42; + eventContext.executeActiveEventsImmediately(); + EXPECT_EQ(calledWith, 42); + + *obs = 45; + eventContext.executeActiveEventsImmediately(); + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, ModifyNowIncludesEventExecution) + { + Observed obs; + + int calledWith = 77; + listen(globalEventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + obs.value() = 42; + obs.modifyNow(); + + EXPECT_EQ(calledWith, 42); + } + + TEST_F(TestEvents, ModifyNowIncludesEventExecutionWithCustomContext) + { + EventContext eventContext; + + Observed obs{&eventContext}; + + int calledWith = 77; + listen(eventContext, obs, [&calledWith](int const& value) -> bool { + calledWith = value; + return true; + }); + + obs.value() = 42; + obs.modifyNow(); + + EXPECT_EQ(calledWith, 42); + } +} \ No newline at end of file diff --git a/nui/test/nui/tests.cpp b/nui/test/nui/tests.cpp index b4acb30d..6e345f74 100644 --- a/nui/test/nui/tests.cpp +++ b/nui/test/nui/tests.cpp @@ -3,6 +3,7 @@ #include "test_ranges.hpp" #include "test_render.hpp" #include "test_switch.hpp" +#include "test_events.hpp" #include "test_selectables_registry.hpp" #include "components/test_table.hpp" #include "components/test_dialog.hpp" diff --git a/tools/inline_injector/main.cpp b/tools/inline_injector/main.cpp index f00ed3cb..12e346e6 100644 --- a/tools/inline_injector/main.cpp +++ b/tools/inline_injector/main.cpp @@ -61,7 +61,7 @@ int main(int argc, char** argv) "\t\n"; // find end of header from behind in indexHtml: - const auto headEnd = indexHtml.rfind(""); + auto headEnd = indexHtml.rfind(""); if (headEnd == std::string::npos) { std::cout << "Could not find in " << index << "\n"; @@ -91,6 +91,13 @@ int main(int argc, char** argv) // insert importStylesHtml before headEnd: indexHtml.insert(insertPoint, importStylesHtml); + headEnd = indexHtml.rfind(""); + if (headEnd == std::string::npos) + { + std::cout << "Could not find in " << index << "\n"; + return 1; + } + // insert importBinIndexHtml before headEnd (always): if (indexHtml.find(binIndex.generic_string()) == std::string::npos) indexHtml.insert(headEnd, importBinIndexHtml);