From c7278e6bbfa0d272d018a553455173527f72aca7 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Thu, 18 Jul 2024 04:03:35 +0200 Subject: [PATCH 01/20] Added factory for elements not in dom. --- .../attributes/impl/attribute_factory.hpp | 52 +++++++++++--- .../nui/frontend/dom/basic_element.hpp | 4 ++ .../nui/frontend/dom/childless_element.hpp | 4 ++ nui/include/nui/frontend/dom/element.hpp | 46 +++++++++++++ .../frontend/elements/impl/materialize.hpp | 10 ++- nui/test/nui/emscripten_mock/emscripten/val.h | 2 +- nui/test/nui/test_attributes.hpp | 10 +++ nui/test/nui/test_elements.hpp | 67 +++++++++++++++++++ nui/test/nui/tests.cpp | 1 + 9 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 nui/test/nui/test_elements.hpp diff --git a/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp b/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp index db98ce06..7be8ff4d 100644 --- a/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp +++ b/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp @@ -70,9 +70,12 @@ namespace Nui::Attributes : name_{name} {} - // Dont use this class like a value - PropertyFactory(PropertyFactory const&) = delete; - PropertyFactory(PropertyFactory&&) = delete; + explicit constexpr PropertyFactory(PropertyFactory const& other) + : name_{other.name_} + {} + explicit constexpr PropertyFactory(PropertyFactory&& other) + : name_{other.name_} + {} PropertyFactory& operator=(PropertyFactory const&) = delete; PropertyFactory& operator=(PropertyFactory&&) = delete; @@ -181,9 +184,12 @@ namespace Nui::Attributes : name_{name} {} - // Dont use this class like a value - AttributeFactory(AttributeFactory const&) = delete; - AttributeFactory(AttributeFactory&&) = delete; + explicit constexpr AttributeFactory(AttributeFactory const& other) + : name_{other.name_} + {} + explicit constexpr AttributeFactory(AttributeFactory&& other) + : name_{other.name_} + {} AttributeFactory& operator=(AttributeFactory const&) = delete; AttributeFactory& operator=(AttributeFactory&&) = delete; @@ -343,9 +349,12 @@ namespace Nui::Attributes : name_{name} {} - // Dont use this class like a value - EventFactory(EventFactory const&) = delete; - EventFactory(EventFactory&&) = delete; + explicit constexpr EventFactory(EventFactory const& other) + : name_{other.name_} + {} + explicit constexpr EventFactory(EventFactory&& other) + : name_{other.name_} + {} EventFactory& operator=(EventFactory const&) = delete; EventFactory& operator=(EventFactory&&) = delete; @@ -397,6 +406,31 @@ namespace Nui::Attributes return EventFactory{name}; } } + + namespace Detail + { + template + struct DeferWrap + { + T factory; + + template + Attribute operator=(Args&&... args) const + { + auto attr = factory.operator=(std::forward(args)...); + attr.defer(true); + return attr; + }; + }; + } + + template + requires( + std::is_same_v || std::is_same_v || std::is_same_v) + Detail::DeferWrap operator!(T const& factory) + { + return Detail::DeferWrap{.factory = T{std::move(factory)}}; + } } #define MAKE_HTML_VALUE_ATTRIBUTE_RENAME(NAME, HTML_NAME) \ diff --git a/nui/include/nui/frontend/dom/basic_element.hpp b/nui/include/nui/frontend/dom/basic_element.hpp index 0a66e6b5..4a11aca2 100644 --- a/nui/include/nui/frontend/dom/basic_element.hpp +++ b/nui/include/nui/frontend/dom/basic_element.hpp @@ -65,6 +65,10 @@ namespace Nui::Dom } protected: + explicit BasicElement() + : element_{Nui::val::undefined()} + {} + Nui::val element_; }; } \ No newline at end of file diff --git a/nui/include/nui/frontend/dom/childless_element.hpp b/nui/include/nui/frontend/dom/childless_element.hpp index 989344cd..7a98d163 100644 --- a/nui/include/nui/frontend/dom/childless_element.hpp +++ b/nui/include/nui/frontend/dom/childless_element.hpp @@ -159,6 +159,10 @@ namespace Nui::Dom } protected: + explicit ChildlessElement() + : BasicElement{} + {} + static Nui::val createElement(HtmlElement const& element) { return element.bridge()->createElement(element); diff --git a/nui/include/nui/frontend/dom/element.hpp b/nui/include/nui/frontend/dom/element.hpp index 932c6f3f..002996fb 100644 --- a/nui/include/nui/frontend/dom/element.hpp +++ b/nui/include/nui/frontend/dom/element.hpp @@ -53,6 +53,12 @@ namespace Nui::Dom , deferredSetup_{} {} + /** + * @brief This constructor takes ownership of a val. + * This val must not be managed by any other element. + * + * @param val + */ explicit Element(Nui::val val) : ChildlessElement{std::move(val)} , children_{} @@ -60,6 +66,13 @@ namespace Nui::Dom , deferredSetup_{} {} + explicit Element() + : ChildlessElement{} + , children_{} + , unsetup_{} + , deferredSetup_{} + {} + Element(Element const&) = delete; Element(Element&&) = delete; Element& operator=(Element const&) = delete; @@ -128,6 +141,22 @@ namespace Nui::Dom replaceElementImpl(element); return shared_from_base(); } + auto emplaceElement(HtmlElement const& element) + { + if (!element_.isUndefined()) + throw std::runtime_error("Element is not empty, cannot emplace"); + + element_ = createElement(element); + setup(element); + if (deferredSetup_) + deferredSetup_(element); + return shared_from_base(); + } + auto emplaceElement(std::invocable auto&& fn) + { + fn(*this, Renderer{.type = RendererType::Emplace}); + return shared_from_base(); + } void setTextContent(std::string const& text) { @@ -275,6 +304,11 @@ namespace Nui::Dom return children_.size(); } + std::string tagName() const + { + return element_["tagName"].as(); + } + private: void replaceElementImpl(HtmlElement const& element) { @@ -283,6 +317,11 @@ namespace Nui::Dom unsetup_(); unsetup_ = {}; +#ifndef NDEBUG + if (element_.isUndefined()) + throw std::runtime_error("Element is undefined"); +#endif + auto replacement = createElement(element); element_.call("replaceWith", replacement); element_ = std::move(replacement); @@ -298,6 +337,13 @@ namespace Nui::Dom std::function unsetup_; std::function deferredSetup_; }; + + inline std::shared_ptr makeStandaloneElement(std::invocable auto&& fn) + { + auto elem = std::make_shared(); + fn(*elem, Renderer{.type = RendererType::Emplace}); + return elem; + } } #include diff --git a/nui/include/nui/frontend/elements/impl/materialize.hpp b/nui/include/nui/frontend/elements/impl/materialize.hpp index b78b710c..8fd19fc9 100644 --- a/nui/include/nui/frontend/elements/impl/materialize.hpp +++ b/nui/include/nui/frontend/elements/impl/materialize.hpp @@ -27,6 +27,11 @@ namespace Nui { return element.replaceElement(htmlElement); } + /// Replaces the given element with the new one. + inline auto emplaceMaterialize(auto& element, auto const& htmlElement) + { + return element.emplaceElement(htmlElement); + } /// Used for elements that dont have a direct parent. inline auto inplaceMaterialize(auto& element, auto const&) { @@ -40,7 +45,8 @@ namespace Nui Fragment, Insert, Replace, - Inplace + Inplace, + Emplace }; struct Renderer { @@ -61,6 +67,8 @@ namespace Nui return Materializers::replaceMaterialize(element, htmlElement); case RendererType::Inplace: return Materializers::inplaceMaterialize(element, htmlElement); + case RendererType::Emplace: + return Materializers::emplaceMaterialize(element, htmlElement); } }; } \ No newline at end of file diff --git a/nui/test/nui/emscripten_mock/emscripten/val.h b/nui/test/nui/emscripten_mock/emscripten/val.h index 980f340b..14e6f21c 100644 --- a/nui/test/nui/emscripten_mock/emscripten/val.h +++ b/nui/test/nui/emscripten_mock/emscripten/val.h @@ -291,7 +291,7 @@ namespace emscripten #ifdef NUI_TEST_DEBUG_PRINT std::cout << "val::undefined()\n"; #endif - return {}; + return Nui::Tests::Engine::createValue(); } static val null() diff --git a/nui/test/nui/test_attributes.hpp b/nui/test/nui/test_attributes.hpp index 9a4bfc3c..3d1d457b 100644 --- a/nui/test/nui/test_attributes.hpp +++ b/nui/test/nui/test_attributes.hpp @@ -539,4 +539,14 @@ namespace Nui::Tests EXPECT_FALSE(Nui::val::global("document")["body"]["attributes"].hasOwnProperty("id")); } + + TEST_F(TestAttributes, CanSetDeferredAttribute) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + render(div{!id = "hi"}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "hi"); + } } \ No newline at end of file diff --git a/nui/test/nui/test_elements.hpp b/nui/test/nui/test_elements.hpp new file mode 100644 index 00000000..f8f810c8 --- /dev/null +++ b/nui/test/nui/test_elements.hpp @@ -0,0 +1,67 @@ +#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 TestElements : public CommonTestFixture + {}; + + TEST_F(TestElements, CanMakeStandaloneElement) + { + using Nui::Elements::div; + + EXPECT_NO_THROW(std::shared_ptr element = Nui::Dom::makeStandaloneElement(div{}("Test"))); + } + + TEST_F(TestElements, StandaloneElementIsOfCorrectType) + { + using Nui::Elements::button; + + std::shared_ptr element = Nui::Dom::makeStandaloneElement(button{}("Test")); + + EXPECT_EQ(element->tagName(), "button"s); + } + + TEST_F(TestElements, StandaloneElementTestContentIsSet) + { + using Nui::Elements::div; + + std::shared_ptr element = Nui::Dom::makeStandaloneElement(div{}("Test")); + + EXPECT_EQ(element->val()["textContent"].as(), "Test"s); + } + + TEST_F(TestElements, AttributesOnStandaloneElementAreSet) + { + using Nui::Elements::div; + using Nui::Attributes::class_; + + std::shared_ptr element = Nui::Dom::makeStandaloneElement(div{class_ = "hi"}("Test")); + + EXPECT_EQ(element->val()["attributes"]["class"].as(), "hi"s); + } + + TEST_F(TestElements, CanSetDeferredAttributesOnStandaloneElement) + { + using Nui::Elements::div; + using Nui::Attributes::class_; + + std::function deferred; + std::shared_ptr element = Nui::Dom::makeStandaloneElement(div{!class_ = "hi"}("Test")); + + EXPECT_EQ(element->val()["attributes"]["class"].as(), "hi"s); + } +} \ No newline at end of file diff --git a/nui/test/nui/tests.cpp b/nui/test/nui/tests.cpp index 6e345f74..83c9203f 100644 --- a/nui/test/nui/tests.cpp +++ b/nui/test/nui/tests.cpp @@ -4,6 +4,7 @@ #include "test_render.hpp" #include "test_switch.hpp" #include "test_events.hpp" +#include "test_elements.hpp" #include "test_selectables_registry.hpp" #include "components/test_table.hpp" #include "components/test_dialog.hpp" From 79981850ba60340a9e98524f716e1a27603e81c1 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 20 Jul 2024 00:01:55 +0200 Subject: [PATCH 02/20] Fixed assert for non browser contexts. --- nui/include/nui/utility/assert.hpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/nui/include/nui/utility/assert.hpp b/nui/include/nui/utility/assert.hpp index b1ad1202..a95a8304 100644 --- a/nui/include/nui/utility/assert.hpp +++ b/nui/include/nui/utility/assert.hpp @@ -1,6 +1,10 @@ #pragma once -#include +#if !defined(__cpp_exceptions) && !defined(__EMSCRIPTEN__) +# include +#else +# include +#endif #include @@ -12,8 +16,11 @@ namespace Nui { #ifdef __cpp_exceptions throw std::runtime_error(message); -#else +#elif defined(__EMSCRIPTEN__) Nui::val::global("console").call("error", message); +#else + std::cerr << message << std::endl; + std::terminate(); #endif } } From eb9cc80efdd23ec8b9aae315c235fe8ca3f3136a Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 20 Jul 2024 04:29:43 +0200 Subject: [PATCH 03/20] Allowed weak_ptr wrapped Observed. --- .../nui/event_system/event_context.hpp | 1 + .../nui/event_system/observed_value.hpp | 104 +++++++++++ .../observed_value_combinator.hpp | 78 +++++--- nui/include/nui/event_system/range.hpp | 35 +--- .../attributes/impl/attribute_factory.hpp | 175 +++++++++++++++++- nui/include/nui/frontend/attributes/style.hpp | 54 +++++- nui/include/nui/frontend/dom/element.hpp | 14 +- nui/include/nui/frontend/property.hpp | 22 ++- .../frontend/attributes/impl/attribute.cpp | 2 +- nui/test/nui/test_attributes.hpp | 148 +++++++++++++++ nui/test/nui/test_properties.hpp | 74 ++++++++ 11 files changed, 635 insertions(+), 72 deletions(-) diff --git a/nui/include/nui/event_system/event_context.hpp b/nui/include/nui/event_system/event_context.hpp index 8275c0d7..d2ad5b56 100644 --- a/nui/include/nui/event_system/event_context.hpp +++ b/nui/include/nui/event_system/event_context.hpp @@ -38,6 +38,7 @@ namespace Nui { public: using EventIdType = EventRegistry::EventIdType; + constexpr static auto invalidEventId = EventRegistry::invalidEventId; EventContext() : impl_{std::make_shared()} diff --git a/nui/include/nui/event_system/observed_value.hpp b/nui/include/nui/event_system/observed_value.hpp index 19063d39..0bb2d0ab 100644 --- a/nui/include/nui/event_system/observed_value.hpp +++ b/nui/include/nui/event_system/observed_value.hpp @@ -17,6 +17,7 @@ #include #include #include +#include namespace Nui { @@ -1259,6 +1260,37 @@ namespace Nui { static constexpr bool value = true; }; + + template + struct IsWeakObserved + { + static constexpr bool value = false; + }; + + template + struct IsWeakObserved>> + { + static constexpr bool value = true; + }; + + template + struct IsSharedObserved + { + static constexpr bool value = false; + }; + + template + struct IsSharedObserved>> + { + static constexpr bool value = true; + }; + + template + struct IsObservedLike + { + static constexpr bool value = + IsObserved::value || IsWeakObserved::value || IsSharedObserved::value; + }; } template @@ -1299,6 +1331,12 @@ namespace Nui template concept IsObserved = Detail::IsObserved>::value; + template + concept IsSharedObserved = Detail::IsSharedObserved>::value; + template + concept IsWeakObserved = Detail::IsWeakObserved>::value; + template + concept IsObservedLike = Detail::IsObservedLike>::value; namespace Detail { @@ -1328,5 +1366,71 @@ namespace Nui private: Observed const* observed_; }; + + template + struct ObservedAddReference + { + using type = T const&; + }; + template <> + struct ObservedAddReference + { + using type = void; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddReference>> + { + using type = std::weak_ptr>; + }; + template + using ObservedAddReference_t = typename ObservedAddReference::type; } + + template + struct UnpackObserved + { + using type = T; + }; + template + struct UnpackObserved> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + struct UnpackObserved>> + { + using type = T; + }; + template + using UnpackObserved_t = typename UnpackObserved::type; } \ 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 index 1205e7cb..e05c9dce 100644 --- a/nui/include/nui/event_system/observed_value_combinator.hpp +++ b/nui/include/nui/event_system/observed_value_combinator.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -12,51 +13,80 @@ namespace Nui class ObservedValueCombinatorBase { public: - explicit constexpr ObservedValueCombinatorBase(ObservedValues const&... observedValues) - : observedValues_{observedValues...} + explicit constexpr ObservedValueCombinatorBase( + Detail::ObservedAddReference_t&&... observedValues) + : observedValues_{std::forward>(observedValues)...} {} - explicit constexpr ObservedValueCombinatorBase(std::tuple 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); - }); + tupleForEach( + observedValues_, + Nui::overloaded{ + [eventId](IsObserved auto const& observed) { + observed.attachEvent(eventId); + }, + [eventId](IsWeakObserved auto const& observed) { + if (auto shared = observed.lock(); shared) + shared->attachEvent(eventId); + }, + }); } constexpr void attachOneshotEvent(auto eventId) const { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.attachOneshotEvent(eventId); - }); + tupleForEach( + observedValues_, + Nui::overloaded{ + [eventId](IsObserved auto const& observed) { + observed.attachOneshotEvent(eventId); + }, + [eventId](IsWeakObserved auto const& observed) { + if (auto shared = observed.lock(); shared) + shared->attachOneshotEvent(eventId); + }, + }); } constexpr void detachEvent(auto eventId) const { - tupleForEach(observedValues_, [eventId](auto const& observed) { - observed.detachEvent(eventId); - }); + tupleForEach( + observedValues_, + Nui::overloaded{ + [eventId](IsObserved auto const& observed) { + observed.detachEvent(eventId); + }, + [eventId](IsWeakObserved auto const& observed) { + if (auto shared = observed.lock(); shared) + shared->detachEvent(eventId); + }, + }); } - std::tuple const& observedValues() & + std::tuple...> const& observedValues() & { return observedValues_; } - std::tuple&& observedValues() && + std::tuple...>&& observedValues() && { - return std::move(const_cast&>(observedValues_)); + return std::move( + const_cast...>&>(observedValues_)); } protected: - const std::tuple observedValues_; + const std::tuple...> observedValues_; }; template - ObservedValueCombinatorBase(std::tuple) -> ObservedValueCombinatorBase; + ObservedValueCombinatorBase(std::tuple...>) + -> ObservedValueCombinatorBase; template - ObservedValueCombinatorBase(ObservedValues const&...) -> ObservedValueCombinatorBase; + ObservedValueCombinatorBase(Detail::ObservedAddReference_t...) + -> ObservedValueCombinatorBase; template class ObservedValueCombinator; @@ -66,7 +96,7 @@ namespace Nui { public: constexpr ObservedValueCombinatorWithGenerator( - std::tuple observedValues, + std::tuple...> observedValues, RendererType generator) : ObservedValueCombinatorBase{std::move(observedValues)} , generator_{std::move(generator)} @@ -138,12 +168,14 @@ namespace Nui } }; template - ObservedValueCombinator(ObservedValues const&...) -> ObservedValueCombinator; + ObservedValueCombinator(Detail::ObservedAddReference_t...) + -> ObservedValueCombinator; template - requires(Detail::IsObserved::value && ...) - ObservedValueCombinator observe(ObservedValues const&... observedValues) + requires(IsObservedLike && ...) + ObservedValueCombinator observe(ObservedValues&&... observedValues) { - return ObservedValueCombinator(observedValues...); + return ObservedValueCombinator{ + std::forward>(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 index 4759225c..3c37dedb 100644 --- a/nui/include/nui/event_system/range.hpp +++ b/nui/include/nui/event_system/range.hpp @@ -92,39 +92,21 @@ namespace Nui template UnoptimizedRange, Observed...> - range(ContainerT const& container, Observed const&... observed) + range(ContainerT const& container, Observed&&... observed) { return UnoptimizedRange, Observed...>{ - ObservedValueCombinator{observed...}, + ObservedValueCombinator...>{ + std::forward>(observed)...}, IteratorAccessor{container}, }; } template - UnoptimizedRange, Observed...> - range(ContainerT& container, Observed const&... observed) + UnoptimizedRange, Observed...> range(ContainerT& container, Observed&&... 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...}, + ObservedValueCombinator...>{ + std::forward>(observed)...}, IteratorAccessor{container}, }; } @@ -148,10 +130,11 @@ namespace Nui #ifdef NUI_HAS_STD_RANGES template UnoptimizedRange>, Observed...> - range(T const& container, Observed const&... observed) + range(T const& container, Observed&&... observed) { return UnoptimizedRange>, Observed...>{ - ObservedValueCombinator{observed...}, + ObservedValueCombinator...>{ + std::forward>(observed)...}, std::ranges::subrange>{ std::ranges::begin(container), std::ranges::end(container)}, }; diff --git a/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp b/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp index 7be8ff4d..472e3f5e 100644 --- a/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp +++ b/nui/include/nui/frontend/attributes/impl/attribute_factory.hpp @@ -86,7 +86,7 @@ namespace Nui::Attributes template requires( - !IsObserved> && !std::invocable && !std::invocable && + !IsObservedLike> && !std::invocable && !std::invocable && !Nui::Detail::IsProperty>) Attribute operator=(U val) const { @@ -97,6 +97,60 @@ namespace Nui::Attributes }; } + template + requires(IsSharedObserved>) + Attribute operator=(U const& shared) const + { + return Attribute{ + [name = name(), weak = std::weak_ptr{shared}](Dom::ChildlessElement& element) { + if (auto shared = weak.lock(); shared) + element.setProperty(name, shared->value()); + }, + [name = name(), weak = std::weak_ptr{shared}](std::weak_ptr&& element) { + auto shared = weak.lock(); + if (!shared) + return EventContext::invalidEventId; + + const auto eventId = globalEventContext.registerEvent(Event{ + [name, element, obsWeak = std::weak_ptr{shared}](auto eventId) { + auto obsShared = obsWeak.lock(); + if (!obsShared) + { + return false; + } + if (auto shared = element.lock(); shared) + { + shared->setProperty(name, obsShared->value()); + return true; + } + obsShared->detachEvent(eventId); + return false; + }, + [element, obsWeak = std::weak_ptr{shared}]() { + return !element.expired() && !obsWeak.expired(); + }, + }); + shared->attachEvent(eventId); + return eventId; + }, + [weak = std::weak_ptr{shared}](EventContext::EventIdType id) { + if (auto shared = weak.lock(); shared) + shared->detachEvent(id); + }, + }; + } + + template + requires(IsWeakObserved>) + Attribute operator=(U&& val) const + { + auto shared = val.lock(); + if (!shared) + return Attribute{}; + + return operator=(shared); + } + template requires(IsObserved>) Attribute operator=(U& val) const @@ -109,7 +163,7 @@ namespace Nui::Attributes return Detail::defaultPropertyEvent( std::move(element), Nui::Detail::CopyableObservedWrap{val}, name); }, - [&val](EventContext::EventIdType const& id) { + [&val](EventContext::EventIdType id) { val.detachEvent(id); }, }; @@ -126,7 +180,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultPropertyEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -143,7 +197,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultPropertyEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -200,7 +254,7 @@ namespace Nui::Attributes template requires( - !IsObserved> && !std::invocable && !std::invocable && + !IsObservedLike> && !std::invocable && !std::invocable && !Nui::Detail::IsProperty>) Attribute operator=(U val) const { @@ -211,6 +265,60 @@ namespace Nui::Attributes }; } + template + requires(IsSharedObserved>) + Attribute operator=(U const& shared) const + { + return Attribute{ + [name = name(), weak = std::weak_ptr{shared}](Dom::ChildlessElement& element) { + if (auto shared = weak.lock(); shared) + element.setAttribute(name, shared->value()); + }, + [name = name(), weak = std::weak_ptr{shared}](std::weak_ptr&& element) { + auto shared = weak.lock(); + if (!shared) + return EventContext::invalidEventId; + + const auto eventId = globalEventContext.registerEvent(Event{ + [name, element, obsWeak = std::weak_ptr{shared}](auto eventId) { + auto obsShared = obsWeak.lock(); + if (!obsShared) + { + return false; + } + if (auto shared = element.lock(); shared) + { + shared->setAttribute(name, obsShared->value()); + return true; + } + obsShared->detachEvent(eventId); + return false; + }, + [element, obsWeak = std::weak_ptr{shared}]() { + return !element.expired() && !obsWeak.expired(); + }, + }); + shared->attachEvent(eventId); + return eventId; + }, + [weak = std::weak_ptr{shared}](EventContext::EventIdType id) { + if (auto shared = weak.lock(); shared) + shared->detachEvent(id); + }, + }; + } + + template + requires(IsWeakObserved>) + Attribute operator=(U&& val) const + { + auto shared = val.lock(); + if (!shared) + return Attribute{}; + + return operator=(shared); + } + template requires(IsObserved>) Attribute operator=(U& val) const @@ -223,7 +331,7 @@ namespace Nui::Attributes return Detail::defaultAttributeEvent( std::move(element), Nui::Detail::CopyableObservedWrap{val}, name); }, - [&val](EventContext::EventIdType const& id) { + [&val](EventContext::EventIdType id) { val.detachEvent(id); }, }; @@ -241,14 +349,61 @@ namespace Nui::Attributes return Detail::defaultPropertyEvent( std::move(element), Nui::Detail::CopyableObservedWrap{*p}, name); }, - [p = prop.prop](EventContext::EventIdType const& id) { + [p = prop.prop](EventContext::EventIdType id) { p->detachEvent(id); }, }; } template - requires(!IsObserved> && !std::invocable && !std::invocable) + requires(IsWeakObserved>) + Attribute operator=(Nui::Detail::Property const& prop) const + { + auto shared = prop.prop.lock(); + if (!shared) + return Attribute{}; + + return Attribute{ + [name = name(), weak = std::weak_ptr{shared}](Dom::ChildlessElement& element) { + if (auto shared = weak.lock(); shared) + element.setProperty(name, shared->value()); + }, + [name = name(), weak = std::weak_ptr{shared}](std::weak_ptr&& element) { + auto shared = weak.lock(); + if (!shared) + return EventContext::invalidEventId; + + const auto eventId = globalEventContext.registerEvent(Event{ + [name, element, obsWeak = std::weak_ptr{shared}](auto eventId) { + auto obsShared = obsWeak.lock(); + if (!obsShared) + { + return false; + } + if (auto shared = element.lock(); shared) + { + shared->setProperty(name, obsShared->value()); + return true; + } + obsShared->detachEvent(eventId); + return false; + }, + [element, obsWeak = std::weak_ptr{shared}]() { + return !element.expired() && !obsWeak.expired(); + }, + }); + shared->attachEvent(eventId); + return eventId; + }, + [weak = std::weak_ptr{shared}](EventContext::EventIdType id) { + if (auto shared = weak.lock(); shared) + shared->detachEvent(id); + }, + }; + } + + template + requires(!IsObservedLike> && !std::invocable && !std::invocable) Attribute operator=(Nui::Detail::Property const& prop) const { return Attribute{[name = name(), p = std::move(prop.prop)](Dom::ChildlessElement& element) { @@ -267,7 +422,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultPropertyEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; @@ -284,7 +439,7 @@ namespace Nui::Attributes [name = name(), combinator](std::weak_ptr&& element) { return Detail::defaultAttributeEvent(std::move(element), combinator, name); }, - [combinator](EventContext::EventIdType const& id) { + [combinator](EventContext::EventIdType id) { combinator.detachEvent(id); }, }; diff --git a/nui/include/nui/frontend/attributes/style.hpp b/nui/include/nui/frontend/attributes/style.hpp index a229186b..cab22cc6 100644 --- a/nui/include/nui/frontend/attributes/style.hpp +++ b/nui/include/nui/frontend/attributes/style.hpp @@ -26,7 +26,7 @@ namespace Nui::Attributes template struct StylePropertyEbo { - std::tuple const&...> observed; + std::tuple<::Nui::Detail::ObservedAddReference_t...> observed; }; template <> struct StylePropertyEbo @@ -35,12 +35,12 @@ namespace Nui::Attributes template struct StylePropertyImpl : public Detail::StylePropertyEbo { - using value_type = std::tuple const&...>; + using value_type = std::tuple<::Nui::Detail::ObservedAddReference_t...>; FunctionT generator; constexpr static bool isStatic() { - return sizeof...(T) == 1 && std::is_same_v, void>; + return sizeof...(T) == 1 && std::is_same_v>, void>; } constexpr auto operator()() const { @@ -52,6 +52,16 @@ namespace Nui::Attributes : Detail::StylePropertyEbo{std::forward_as_tuple(observed)} , generator{std::move(generator)} {} + template + constexpr StylePropertyImpl(FunctionT generator, std::weak_ptr> observed) + : Detail::StylePropertyEbo{std::make_tuple(std::move(observed))} + , generator{std::move(generator)} + {} + template + constexpr StylePropertyImpl(FunctionT generator, std::shared_ptr> observed) + : Detail::StylePropertyEbo{std::make_tuple(std::weak_ptr{observed})} + , generator{std::move(generator)} + {} constexpr StylePropertyImpl(FunctionT generator, std::nullptr_t) : Detail::StylePropertyEbo{} , generator{std::move(generator)} @@ -59,7 +69,7 @@ namespace Nui::Attributes template constexpr StylePropertyImpl( FunctionT generator, - ObservedValueCombinatorWithGenerator...>&& observed) + ObservedValueCombinatorWithGenerator&& observed) : Detail::StylePropertyEbo{std::move(observed).observedValues()} , generator{std::move(generator)} {} @@ -67,10 +77,18 @@ namespace Nui::Attributes template StylePropertyImpl(FunctionT generator, std::nullptr_t) -> StylePropertyImpl; template - StylePropertyImpl(FunctionT generator, Observed&) -> StylePropertyImpl; + StylePropertyImpl(FunctionT generator, Observed&) -> StylePropertyImpl>; + template + StylePropertyImpl(FunctionT generator, Observed const&) -> StylePropertyImpl>; + template + StylePropertyImpl(FunctionT generator, std::shared_ptr>) + -> StylePropertyImpl>>; + template + StylePropertyImpl(FunctionT generator, std::weak_ptr>&&) + -> StylePropertyImpl>>; template StylePropertyImpl(FunctionT generator, ObservedValueCombinatorWithGenerator&&) - -> StylePropertyImpl; + -> StylePropertyImpl; namespace Detail { @@ -169,7 +187,7 @@ namespace Nui::Attributes }, nullptr}; } - auto operator=(Observed& observedValue) + auto operator=(Observed const& observedValue) { return StylePropertyImpl{ [name_ = std::string{name}, &observedValue]() { @@ -177,6 +195,28 @@ namespace Nui::Attributes }, observedValue}; } + auto operator=(std::weak_ptr>&& observedValue) + { + return StylePropertyImpl{ + [name_ = std::string{name}, observedValue = std::weak_ptr{observedValue.lock()}]() { + if (auto shared = observedValue.lock(); shared) + return name_ + ":" + shared->value(); + // What to do here? + return name_ + ":DEAD_OBSERVED"; + }, + std::move(observedValue)}; + } + auto operator=(std::shared_ptr> observedValue) + { + return StylePropertyImpl{ + [name_ = std::string{name}, observedValue = std::weak_ptr{observedValue}]() { + if (auto shared = observedValue.lock(); shared) + return name_ + ":" + shared->value(); + // What to do here? + return name_ + ":DEAD_OBSERVED"; + }, + std::move(observedValue)}; + } template auto operator=(ObservedValueCombinatorWithGenerator&& combinator) { diff --git a/nui/include/nui/frontend/dom/element.hpp b/nui/include/nui/frontend/dom/element.hpp index 002996fb..420c74a6 100644 --- a/nui/include/nui/frontend/dom/element.hpp +++ b/nui/include/nui/frontend/dom/element.hpp @@ -213,10 +213,13 @@ namespace Nui::Dom auto clear = attribute.getEventClear(); if (clear) { - eventClearers.push_back( - [clear = std::move(clear), id = attribute.createEvent(weak_from_base())]() { + const auto id = attribute.createEvent(weak_from_base()); + if (id != EventContext::invalidEventId) + { + eventClearers.push_back([clear = std::move(clear), id]() { clear(id); }); + } } } if (!eventClearers.empty()) @@ -248,10 +251,13 @@ namespace Nui::Dom auto clear = attribute.getEventClear(); if (clear) { - eventClearers.push_back( - [clear = std::move(clear), id = attribute.createEvent(weak_from_base())]() { + const auto id = attribute.createEvent(weak_from_base()); + if (id != EventContext::invalidEventId) + { + eventClearers.push_back([clear = std::move(clear), id]() { clear(id); }); + } } } if (!eventClearers.empty()) diff --git a/nui/include/nui/frontend/property.hpp b/nui/include/nui/frontend/property.hpp index 75cc479d..0c5fc3df 100644 --- a/nui/include/nui/frontend/property.hpp +++ b/nui/include/nui/frontend/property.hpp @@ -20,6 +20,12 @@ namespace Nui Observed const* prop; }; + template + struct Property>> + { + std::weak_ptr> prop; + }; + template struct IsPropertyImpl { @@ -43,6 +49,20 @@ namespace Nui return Detail::Property>{.prop = &val}; } + template + requires(IsWeakObserved>) + Detail::Property> property(U&& val) + { + return Detail::Property>{.prop = std::forward(val)}; + } + + template + requires(IsSharedObserved>) + Detail::Property::weak_type> property(U const& val) + { + return Detail::Property::weak_type>{.prop = std::weak_ptr{val}}; + } + template requires(std::invocable) Detail::Property> property(U val) @@ -58,7 +78,7 @@ namespace Nui } template - requires(!IsObserved> && !std::invocable && !std::invocable) + requires(!IsObservedLike> && !std::invocable && !std::invocable) Detail::Property> property(U val) { return Detail::Property>{.prop = std::move(val)}; diff --git a/nui/src/nui/frontend/attributes/impl/attribute.cpp b/nui/src/nui/frontend/attributes/impl/attribute.cpp index 99a9d1c2..b8594c57 100644 --- a/nui/src/nui/frontend/attributes/impl/attribute.cpp +++ b/nui/src/nui/frontend/attributes/impl/attribute.cpp @@ -15,7 +15,7 @@ namespace Nui { if (createEvent_) return createEvent_(std::move(element)); - return EventContext::EventIdType{}; + return EventContext::invalidEventId; } //--------------------------------------------------------------------------------------------------------------------- std::function Attribute::getEventClear() const diff --git a/nui/test/nui/test_attributes.hpp b/nui/test/nui/test_attributes.hpp index 3d1d457b..77441381 100644 --- a/nui/test/nui/test_attributes.hpp +++ b/nui/test/nui/test_attributes.hpp @@ -549,4 +549,152 @@ namespace Nui::Tests EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "hi"); } + + TEST_F(TestAttributes, CanUseSharedPointerObserved) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + auto idValue = std::make_shared>("A"); + + render(div{id = idValue}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + + *idValue = "B"; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "B"); + } + + TEST_F(TestAttributes, CanUseWeakPointerObserved) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + auto idValue = std::make_shared>("A"); + auto weakIdValue = std::weak_ptr{idValue}; + + render(div{id = weakIdValue}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + + *idValue = "B"; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "B"); + } + + TEST_F(TestAttributes, CanUseSharedPointerObservedWithDeferred) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + auto idValue = std::make_shared>("A"); + + render(div{!id = idValue}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + + *idValue = "B"; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "B"); + } + + TEST_F(TestAttributes, CanUseWeakPointerObservedWithDeferred) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + auto idValue = std::make_shared>("A"); + auto weakIdValue = std::weak_ptr{idValue}; + + render(div{!id = weakIdValue}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + + *idValue = "B"; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "B"); + } + + TEST_F(TestAttributes, WeakPointerAttributeDoesNotFailOnExpired) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + auto idValue = std::make_shared>("A"); + auto weakIdValue = std::weak_ptr{idValue}; + + render(div{id = weakIdValue}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + + { + *idValue = "B"; + } + idValue = nullptr; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + } + + TEST_F(TestAttributes, SharedPointerAttributeDoesNotFailOnExpired) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + auto idValue = std::make_shared>("A"); + + render(div{id = idValue}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + + { + *idValue = "B"; + } + idValue = nullptr; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["attributes"]["id"].as(), "A"); + } + + TEST_F(TestAttributes, SharedPointerMayExpireBeforeDetach) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + Nui::Observed other{true}; + auto idValue = std::make_shared>("A"); + + render(div{}(observe(other), [&idValue]() -> Nui::ElementRenderer { + return div{id = idValue}(); + })); + + EXPECT_EQ(Nui::val::global("document")["body"]["children"][0]["attributes"]["id"].as(), "A"); + + other = false; + EXPECT_NO_FATAL_FAILURE(globalEventContext.executeActiveEventsImmediately()); + } + + TEST_F(TestAttributes, WeakPointerMayExpireBeforeDetach) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + Nui::Observed other{true}; + auto idValue = std::make_shared>("A"); + auto weakIdValue = std::weak_ptr{idValue}; + + render(div{}(observe(other), [&weakIdValue]() -> Nui::ElementRenderer { + return div{id = weakIdValue}(); + })); + + EXPECT_EQ(Nui::val::global("document")["body"]["children"][0]["attributes"]["id"].as(), "A"); + + other = false; + EXPECT_NO_FATAL_FAILURE(globalEventContext.executeActiveEventsImmediately()); + } } \ No newline at end of file diff --git a/nui/test/nui/test_properties.hpp b/nui/test/nui/test_properties.hpp index 332345a0..a86032a8 100644 --- a/nui/test/nui/test_properties.hpp +++ b/nui/test/nui/test_properties.hpp @@ -275,4 +275,78 @@ namespace Nui::Tests EXPECT_EQ(Nui::val::global("document")["body"]["value"].as(), "Goodbye World"); } + + TEST_F(TestProperties, CanUseWeakPointerObservedInProperty) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + std::shared_ptr> value = std::make_shared>("A"); + + render(div{id = property(std::weak_ptr{value})}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "A"); + + *value = "B"; + Nui::globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "B"); + } + + TEST_F(TestProperties, CanUseSharedPointerObservedInProperty) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + std::shared_ptr> value = std::make_shared>("A"); + + render(div{id = property(value)}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "A"); + + *value = "B"; + Nui::globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "B"); + } + + TEST_F(TestProperties, WeakPointerPropertyDoesNotFailOnExpired) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + std::shared_ptr> value = std::make_shared>("A"); + + render(div{id = property(std::weak_ptr{value})}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "A"); + + { + *value = "B"; + } + value = nullptr; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "A"); + } + + TEST_F(TestProperties, SharedPointerPropertyDoesNotFailOnExpired) + { + using Nui::Elements::div; + using Nui::Attributes::id; + + std::shared_ptr> value = std::make_shared>("A"); + + render(div{id = property(value)}()); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "A"); + + { + *value = "B"; + } + value = nullptr; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(Nui::val::global("document")["body"]["id"].as(), "A"); + } } \ No newline at end of file From 67ff258489b162ed78e3c00712e92fac78c3122d Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 20 Jul 2024 18:28:37 +0200 Subject: [PATCH 04/20] Fixed all shared regressions. Also added tests. --- .../nui/event_system/event_context.hpp | 2 +- .../nui/event_system/observed_value.hpp | 56 +++- .../observed_value_combinator.hpp | 39 ++- nui/include/nui/event_system/range.hpp | 44 ++-- nui/include/nui/frontend/attributes/style.hpp | 15 +- .../frontend/elements/impl/html_element.hpp | 4 +- .../frontend/elements/impl/range_renderer.tpp | 5 +- nui/include/nui/utility/iterator_accessor.hpp | 92 ++++++- nui/test/nui/test_attributes.hpp | 67 +++++ nui/test/nui/test_ranges.hpp | 247 ++++++++++++++++++ nui/test/nui/test_render.hpp | 116 ++++++++ 11 files changed, 640 insertions(+), 47 deletions(-) diff --git a/nui/include/nui/event_system/event_context.hpp b/nui/include/nui/event_system/event_context.hpp index d2ad5b56..aa0962a7 100644 --- a/nui/include/nui/event_system/event_context.hpp +++ b/nui/include/nui/event_system/event_context.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include diff --git a/nui/include/nui/event_system/observed_value.hpp b/nui/include/nui/event_system/observed_value.hpp index 0bb2d0ab..d970e48a 100644 --- a/nui/include/nui/event_system/observed_value.hpp +++ b/nui/include/nui/event_system/observed_value.hpp @@ -2,8 +2,8 @@ #include #include -#include -#include +#include +#include #include #include @@ -299,6 +299,12 @@ namespace Nui return ModificationProxy{*this, true}; } + explicit operator bool() const + requires std::convertible_to + { + return static_cast(contained_); + } + ContainedT& value() { return contained_; @@ -397,6 +403,26 @@ namespace Nui std::size_t pos_; T& ref_; }; + + template + auto& unwrapReferenceWrapper(ReferenceWrapper& wrapper) + { + return wrapper.get(); + } + template + auto const& unwrapReferenceWrapper(ReferenceWrapper const& wrapper) + { + return wrapper.get(); + } + auto& unwrapReferenceWrapper(auto& ref) + { + return ref; + } + auto const& unwrapReferenceWrapper(auto const& ref) + { + return ref; + } + template class PointerWrapper { @@ -1397,8 +1423,32 @@ namespace Nui { using type = std::weak_ptr>; }; + + template + struct ObservedAddMutableReference + { + using type = T&; + }; + template <> + struct ObservedAddMutableReference + { + using type = void; + }; + template + struct ObservedAddMutableReference>> + { + using type = std::weak_ptr>; + }; + template + struct ObservedAddMutableReference>> + { + using type = std::weak_ptr>; + }; + + template + using ObservedAddReference_t = typename ObservedAddReference>::type; template - using ObservedAddReference_t = typename ObservedAddReference::type; + using ObservedAddMutableReference_t = typename ObservedAddMutableReference>::type; } template diff --git a/nui/include/nui/event_system/observed_value_combinator.hpp b/nui/include/nui/event_system/observed_value_combinator.hpp index e05c9dce..f3765dba 100644 --- a/nui/include/nui/event_system/observed_value_combinator.hpp +++ b/nui/include/nui/event_system/observed_value_combinator.hpp @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include #include #include #include @@ -78,15 +79,27 @@ namespace Nui const_cast...>&>(observedValues_)); } + bool isAnyExpired() const + { + const auto isExpired = Nui::overloaded{ + [](IsObserved auto const& observed) { + return false; + }, + [](IsWeakObserved auto const& observed) { + return observed.expired(); + }, + }; + + return std::apply( + [isExpired](auto const&... observed) { + return (isExpired(observed) || ...); + }, + observedValues_); + } + protected: const std::tuple...> observedValues_; }; - template - ObservedValueCombinatorBase(std::tuple...>) - -> ObservedValueCombinatorBase; - template - ObservedValueCombinatorBase(Detail::ObservedAddReference_t...) - -> ObservedValueCombinatorBase; template class ObservedValueCombinator; @@ -168,14 +181,18 @@ namespace Nui } }; template - ObservedValueCombinator(Detail::ObservedAddReference_t...) - -> ObservedValueCombinator; + ObservedValueCombinator(ObservedValues&&...) + -> ObservedValueCombinator>...>; + template + ObservedValueCombinator(std::tuple...>) + -> ObservedValueCombinator>...>; template requires(IsObservedLike && ...) - ObservedValueCombinator observe(ObservedValues&&... observedValues) + ObservedValueCombinator>...> + observe(ObservedValues&&... observedValues) { - return ObservedValueCombinator{ + return ObservedValueCombinator>...>{ std::forward>(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 index 3c37dedb..2e7a44c2 100644 --- a/nui/include/nui/event_system/range.hpp +++ b/nui/include/nui/event_system/range.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -21,22 +21,24 @@ namespace Nui static constexpr bool isRandomAccess = ObservedType::isRandomAccess; - explicit constexpr ObservedRange(ObservedValue& observedValues) - : observedValue_{observedValues} + template + requires std::is_same_v, std::decay_t> + explicit constexpr ObservedRange(ObservedValueT&& observedValues) + : observedValue_{std::forward>(observedValues)} {} - ObservedValue const& underlying() const + Detail::ObservedAddReference_t underlying() const { return observedValue_; } - ObservedValue& underlying() + Detail::ObservedAddMutableReference_t underlying() requires(!std::is_const_v) { return observedValue_; } private: - ObservedValue& observedValue_; + Detail::ObservedAddMutableReference_t observedValue_; }; template @@ -91,22 +93,25 @@ namespace Nui } template - UnoptimizedRange, Observed...> + UnoptimizedRange, std::decay_t>...> range(ContainerT const& container, Observed&&... observed) { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator...>{ - std::forward>(observed)...}, + return UnoptimizedRange< + IteratorAccessor, + std::decay_t>...>{ + ObservedValueCombinator{std::forward>(observed)...}, IteratorAccessor{container}, }; } template - UnoptimizedRange, Observed...> range(ContainerT& container, Observed&&... observed) + UnoptimizedRange, std::decay_t>...> + range(ContainerT& container, Observed&&... observed) { - return UnoptimizedRange, Observed...>{ - ObservedValueCombinator...>{ - std::forward>(observed)...}, + return UnoptimizedRange< + IteratorAccessor, + std::decay_t>...>{ + ObservedValueCombinator{std::forward>(observed)...}, IteratorAccessor{container}, }; } @@ -129,12 +134,15 @@ namespace Nui #ifdef NUI_HAS_STD_RANGES template - UnoptimizedRange>, Observed...> + UnoptimizedRange< + std::ranges::subrange>, + std::decay_t>...> range(T const& container, Observed&&... observed) { - return UnoptimizedRange>, Observed...>{ - ObservedValueCombinator...>{ - std::forward>(observed)...}, + return UnoptimizedRange< + std::ranges::subrange>, + std::decay_t>...>{ + ObservedValueCombinator{std::forward>(observed)...}, std::ranges::subrange>{ std::ranges::begin(container), std::ranges::end(container)}, }; diff --git a/nui/include/nui/frontend/attributes/style.hpp b/nui/include/nui/frontend/attributes/style.hpp index cab22cc6..8611e636 100644 --- a/nui/include/nui/frontend/attributes/style.hpp +++ b/nui/include/nui/frontend/attributes/style.hpp @@ -201,8 +201,7 @@ namespace Nui::Attributes [name_ = std::string{name}, observedValue = std::weak_ptr{observedValue.lock()}]() { if (auto shared = observedValue.lock(); shared) return name_ + ":" + shared->value(); - // What to do here? - return name_ + ":DEAD_OBSERVED"; + return std::string{}; }, std::move(observedValue)}; } @@ -212,8 +211,7 @@ namespace Nui::Attributes [name_ = std::string{name}, observedValue = std::weak_ptr{observedValue}]() { if (auto shared = observedValue.lock(); shared) return name_ + ":" + shared->value(); - // What to do here? - return name_ + ":DEAD_OBSERVED"; + return std::string{}; }, std::move(observedValue)}; } @@ -246,8 +244,9 @@ namespace Nui::Attributes std::stringstream sstr; [&sstr](auto const& head, auto const&... tail) { using expander = int[]; - sstr << head(); - (void)expander{0, (sstr << ";" << tail(), void(), 0)...}; + const auto headStr = head(); + sstr << headStr; + (void)expander{0, (sstr << (headStr.empty() ? "" : ";") << tail(), void(), 0)...}; }(props...); return sstr.str(); }; @@ -328,9 +327,9 @@ namespace Nui::Attributes else { return std::apply( - [&style](ObservedValueTypes&... obs) { + [&style](ObservedValueTypes&&... obs) { return AttributeFactory{"style"}.operator=( - ObservedValueCombinator{obs...}.generate( + ObservedValueCombinator{std::forward(obs)...}.generate( std::move(style).ejectGenerator())); }, std::move(style).ejectObservedValues()); diff --git a/nui/include/nui/frontend/elements/impl/html_element.hpp b/nui/include/nui/frontend/elements/impl/html_element.hpp index 52023f30..8aaf4a1b 100644 --- a/nui/include/nui/frontend/elements/impl/html_element.hpp +++ b/nui/include/nui/frontend/elements/impl/html_element.hpp @@ -111,7 +111,7 @@ namespace Nui fragmentContext.clear(); auto parent = createdSelfWeak.lock(); - if (!parent) + if (!parent || observedValues.isAnyExpired()) { childrenRefabricator.reset(); return; @@ -133,7 +133,7 @@ namespace Nui createdSelfWeak = std::weak_ptr(createdSelf), childrenRefabricator]() mutable { auto parent = createdSelfWeak.lock(); - if (!parent) + if (!parent || observedValues.isAnyExpired()) { childrenRefabricator.reset(); return; diff --git a/nui/include/nui/frontend/elements/impl/range_renderer.tpp b/nui/include/nui/frontend/elements/impl/range_renderer.tpp index 158c6aae..9a413702 100644 --- a/nui/include/nui/frontend/elements/impl/range_renderer.tpp +++ b/nui/include/nui/frontend/elements/impl/range_renderer.tpp @@ -183,9 +183,10 @@ namespace Nui::Detail materialized->clearChildren(); long long counter = 0; - for (auto& element : unoptimizedRange_) + for (auto&& element : unoptimizedRange_) { - elementRenderer_(counter++, element)(*materialized, Renderer{.type = RendererType::Append}); + elementRenderer_(counter++, ContainerWrapUtility::unwrapReferenceWrapper(element))( + *materialized, Renderer{.type = RendererType::Append}); } return KeepRange; diff --git a/nui/include/nui/utility/iterator_accessor.hpp b/nui/include/nui/utility/iterator_accessor.hpp index 36161b2a..49df1849 100644 --- a/nui/include/nui/utility/iterator_accessor.hpp +++ b/nui/include/nui/utility/iterator_accessor.hpp @@ -1,11 +1,11 @@ #pragma once #include - +#include namespace Nui { template - struct IteratorAccessor + class IteratorAccessor { public: using IteratorType = typename ContainerT::iterator; @@ -48,6 +48,94 @@ namespace Nui ContainerT* container_; }; + template + class IteratorAccessor> + { + public: + using IteratorType = typename ContainerT::iterator; + using ConstIteratorType = typename ContainerT::const_iterator; + + explicit IteratorAccessor(std::weak_ptr container) + : container_{std::move(container)} + {} + ~IteratorAccessor() = default; + IteratorAccessor(IteratorAccessor const&) = default; + IteratorAccessor(IteratorAccessor&&) = default; + IteratorAccessor& operator=(IteratorAccessor const&) = default; + IteratorAccessor& operator=(IteratorAccessor&&) = default; + + ConstIteratorType begin() const + requires(std::is_const_v) + { + return container_.lock()->begin(); + } + + ConstIteratorType end() const + requires(std::is_const_v) + { + return container_.lock()->end(); + } + + IteratorType begin() const + requires(!std::is_const_v) + { + return container_.lock()->begin(); + } + + IteratorType end() const + requires(!std::is_const_v) + { + return container_.lock()->end(); + } + + private: + std::weak_ptr container_; + }; + + template + class IteratorAccessor> + { + public: + using IteratorType = typename ContainerT::iterator; + using ConstIteratorType = typename ContainerT::const_iterator; + + explicit IteratorAccessor(std::weak_ptr container) + : container_{std::move(container)} + {} + ~IteratorAccessor() = default; + IteratorAccessor(IteratorAccessor const&) = default; + IteratorAccessor(IteratorAccessor&&) = default; + IteratorAccessor& operator=(IteratorAccessor const&) = default; + IteratorAccessor& operator=(IteratorAccessor&&) = default; + + ConstIteratorType begin() const + requires(std::is_const_v) + { + return container_->begin(); + } + + ConstIteratorType end() const + requires(std::is_const_v) + { + return container_->end(); + } + + IteratorType begin() const + requires(!std::is_const_v) + { + return container_->begin(); + } + + IteratorType end() const + requires(!std::is_const_v) + { + return container_->end(); + } + + private: + std::shared_ptr container_; + }; + // Deduction guide for const ContainerT template IteratorAccessor(ContainerT const&) -> IteratorAccessor; diff --git a/nui/test/nui/test_attributes.hpp b/nui/test/nui/test_attributes.hpp index 77441381..9f15206a 100644 --- a/nui/test/nui/test_attributes.hpp +++ b/nui/test/nui/test_attributes.hpp @@ -358,6 +358,73 @@ namespace Nui::Tests "color:green;background-color:yellow"); } + TEST_F(TestAttributes, StyleAttributeCanUseWeakObserved) + { + using Nui::Elements::div; + using Nui::Attributes::Style; + using Nui::Attributes::style; + using namespace Nui::Attributes::Literals; + + auto color = std::make_shared>("red"); + + render( + div{style = Style{ + "color"_style = std::weak_ptr{color}, + "background-color"_style = "blue", + }}()); + + EXPECT_EQ( + Nui::val::global("document")["body"]["attributes"]["style"].as(), + "color:red;background-color:blue"); + } + + TEST_F(TestAttributes, StyleAttributeCanUseSharedObserved) + { + using Nui::Elements::div; + using Nui::Attributes::Style; + using Nui::Attributes::style; + using namespace Nui::Attributes::Literals; + + auto color = std::make_shared>("red"); + + render( + div{style = Style{ + "color"_style = color, + "background-color"_style = "blue", + }}()); + + EXPECT_EQ( + Nui::val::global("document")["body"]["attributes"]["style"].as(), + "color:red;background-color:blue"); + } + + TEST_F(TestAttributes, StyleAttributeSharedObservedMayExpire) + { + using Nui::Elements::div; + using Nui::Attributes::Style; + using Nui::Attributes::style; + using namespace Nui::Attributes::Literals; + + auto color = std::make_shared>("red"); + + render( + div{style = Style{ + "color"_style = color, + "background-color"_style = "blue", + }}()); + + EXPECT_EQ( + Nui::val::global("document")["body"]["attributes"]["style"].as(), + "color:red;background-color:blue"); + + *color = "blue"; + color = nullptr; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ( + Nui::val::global("document")["body"]["attributes"]["style"].as(), "background-color:blue"); + } + TEST_F(TestAttributes, EventIsCallable) { using Nui::Elements::div; diff --git a/nui/test/nui/test_ranges.hpp b/nui/test/nui/test_ranges.hpp index b874cce8..13b4f813 100644 --- a/nui/test/nui/test_ranges.hpp +++ b/nui/test/nui/test_ranges.hpp @@ -738,4 +738,251 @@ namespace Nui::Tests ++i; } } + + TEST_F(TestRanges, CanUseStaticRangeSharedPointer) + { + std::shared_ptr> characters = + std::make_shared>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(characters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->size())); + for (int i = 0; i != characters->size(); ++i) + { + EXPECT_EQ( + parent["children"][i]["textContent"].as(), + std::string{(*characters)[i]} + ":" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseStaticRangeSharedPointerToConst) + { + std::shared_ptr> characters = + std::make_shared>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(characters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->size())); + for (int i = 0; i != characters->size(); ++i) + { + EXPECT_EQ( + parent["children"][i]["textContent"].as(), + std::string{(*characters)[i]} + ":" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseStaticRangeWeakPointer) + { + std::shared_ptr> characters = + std::make_shared>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + std::weak_ptr> weakCharacters{characters}; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(weakCharacters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->size())); + for (int i = 0; i != characters->size(); ++i) + { + EXPECT_EQ( + parent["children"][i]["textContent"].as(), + std::string{(*characters)[i]} + ":" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseStaticRangeWeakPointerToConst) + { + std::shared_ptr> characters = + std::make_shared>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + std::weak_ptr> weakCharacters{characters}; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(weakCharacters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->size())); + for (int i = 0; i != characters->size(); ++i) + { + EXPECT_EQ( + parent["children"][i]["textContent"].as(), + std::string{(*characters)[i]} + ":" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseSharedStaticRendererTakingNonConst) + { + std::shared_ptr> characters = + std::make_shared>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(characters), [&characters](long long i, auto& element) { + element = 'X'; + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->size())); + for (int i = 0; i != characters->size(); ++i) + { + EXPECT_EQ(parent["children"][i]["textContent"].as(), "X:" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseWeakStaticRendererTakingNonConst) + { + std::shared_ptr> characters = + std::make_shared>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + std::weak_ptr> weakCharacters{characters}; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(weakCharacters), [&characters](long long i, auto& element) { + element = 'X'; + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->size())); + for (int i = 0; i != characters->size(); ++i) + { + EXPECT_EQ(parent["children"][i]["textContent"].as(), "X:" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseObservedRangeSharedPointer) + { + auto characters = std::make_shared>>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(characters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->value().size())); + for (int i = 0; i != characters->value().size(); ++i) + { + EXPECT_EQ( + parent["children"][i]["textContent"].as(), + std::string{characters->value()[i]} + ":" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseObservedRangeWeakPointer) + { + auto characters = std::make_shared>>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + std::weak_ptr>> weakCharacters{characters}; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(weakCharacters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->value().size())); + for (int i = 0; i != characters->value().size(); ++i) + { + EXPECT_EQ( + parent["children"][i]["textContent"].as(), + std::string{characters->value()[i]} + ":" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseObservedRangeSharedPointerRendererTakingNonConst) + { + auto characters = std::make_shared>>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(characters), [&characters](long long i, auto& element) { + element = 'X'; + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->value().size())); + for (int i = 0; i != characters->value().size(); ++i) + { + EXPECT_EQ(parent["children"][i]["textContent"].as(), "X:" + std::to_string(i)); + } + } + + TEST_F(TestRanges, CanUseObservedRangeWeakPointerRendererTakingNonConst) + { + auto characters = std::make_shared>>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + std::weak_ptr>> weakCharacters{characters}; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(weakCharacters), [&characters](long long i, auto& element) { + element = 'X'; + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + EXPECT_EQ(parent["children"]["length"].as(), static_cast(characters->value().size())); + for (int i = 0; i != characters->value().size(); ++i) + { + EXPECT_EQ(parent["children"][i]["textContent"].as(), "X:" + std::to_string(i)); + } + } + + TEST_F(TestRanges, WeakObservedMayExpireErrorlessly) + { + auto characters = std::make_shared>>(std::vector{'A', 'B', 'C', 'D'}); + Nui::val parent; + std::weak_ptr>> weakCharacters{characters}; + + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + render(body{reference = parent}(range(weakCharacters), [&characters](long long i, auto const& element) { + return div{}(std::string{element} + ":" + std::to_string(i)); + })); + + // activates an event + characters->push_back('E'); + characters.reset(); + EXPECT_NO_FATAL_FAILURE(globalEventContext.executeActiveEventsImmediately()); + } } \ No newline at end of file diff --git a/nui/test/nui/test_render.hpp b/nui/test/nui/test_render.hpp index 162fef43..b9131bff 100644 --- a/nui/test/nui/test_render.hpp +++ b/nui/test/nui/test_render.hpp @@ -785,4 +785,120 @@ namespace Nui::Tests EXPECT_EQ(child["attributes"]["class"].as(), expectedClass); } } + + TEST_F(TestRender, CanUseSharedObservedForObserve) + { + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + Nui::val nested; + auto toggle = std::make_shared>(true); + + render(body{}(observe(toggle), [&toggle, &nested]() { + if (*toggle) + { + return div{reference = nested}("Hello"); + } + else + { + return div{reference = nested}("Goodbye"); + } + })); + + EXPECT_EQ(nested["textContent"].as(), "Hello"); + + *toggle = false; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(nested["textContent"].as(), "Goodbye"); + } + + TEST_F(TestRender, CanUseWeakObservedForObserve) + { + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + Nui::val nested; + auto toggle = std::make_shared>(true); + auto weak = std::weak_ptr>{toggle}; + + render(body{}(observe(weak), [&toggle, &nested]() { + if (*toggle) + { + return div{reference = nested}("Hello"); + } + else + { + return div{reference = nested}("Goodbye"); + } + })); + + EXPECT_EQ(nested["textContent"].as(), "Hello"); + + *toggle = false; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(nested["textContent"].as(), "Goodbye"); + } + + TEST_F(TestRender, SharedObservedMayExpire) + { + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + Nui::val nested; + auto toggle = std::make_shared>(true); + + render(body{}(observe(toggle), [&toggle, &nested]() { + if (*toggle) + { + return div{reference = nested}("Hello"); + } + else + { + return div{reference = nested}("Goodbye"); + } + })); + + EXPECT_EQ(nested["textContent"].as(), "Hello"); + + *toggle = false; + toggle.reset(); + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(nested["textContent"].as(), "Hello"); + } + + TEST_F(TestRender, CanMixSharedWeakAndRegularObserved) + { + using Nui::Elements::div; + using Nui::Elements::body; + using namespace Nui::Attributes; + + Nui::val nested; + Observed bla{true}; + auto toggle = std::make_shared>(true); + auto weak = std::weak_ptr>{toggle}; + + render(body{}(observe(toggle, weak, bla), [&toggle, &nested]() { + if (*toggle) + { + return div{reference = nested}("Hello"); + } + else + { + return div{reference = nested}("Goodbye"); + } + })); + + EXPECT_EQ(nested["textContent"].as(), "Hello"); + + *toggle = false; + globalEventContext.executeActiveEventsImmediately(); + + EXPECT_EQ(nested["textContent"].as(), "Goodbye"); + } } \ No newline at end of file From 4f1dc85c23a5fed20091884ffd9394f050e81714 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sat, 20 Jul 2024 18:32:06 +0200 Subject: [PATCH 05/20] Fixed warning of unused parameter. --- nui/include/nui/event_system/observed_value_combinator.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nui/include/nui/event_system/observed_value_combinator.hpp b/nui/include/nui/event_system/observed_value_combinator.hpp index f3765dba..de621515 100644 --- a/nui/include/nui/event_system/observed_value_combinator.hpp +++ b/nui/include/nui/event_system/observed_value_combinator.hpp @@ -82,7 +82,7 @@ namespace Nui bool isAnyExpired() const { const auto isExpired = Nui::overloaded{ - [](IsObserved auto const& observed) { + [](IsObserved auto const&) { return false; }, [](IsWeakObserved auto const& observed) { From 77cb2ea823dfbd19b141af10a920059e783bd5d6 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 21 Jul 2024 05:25:27 +0200 Subject: [PATCH 06/20] Added more special paths. --- .../nui/backend/filesystem/special_paths.hpp | 9 +++ .../nui/backend/filesystem/special_paths.cpp | 64 +++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/nui/include/nui/backend/filesystem/special_paths.hpp b/nui/include/nui/backend/filesystem/special_paths.hpp index 7ea7f08a..459a48c9 100644 --- a/nui/include/nui/backend/filesystem/special_paths.hpp +++ b/nui/include/nui/backend/filesystem/special_paths.hpp @@ -13,6 +13,15 @@ namespace Nui * %appdata% Linux: home. Windows: CSIDL_APPDATA * %localappdata% Linux: home. Windows: CSIDL_APPDATA_LOCAL * %temp% Linux: /tmp. Windows: ${USER}\AppData\Local\Temp + * %config_home% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_APPDATA + * %config_home2% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_MYDOCUMENTS + * %config_home3% Linux: $XDG_CONFIG_HOME. Windows: home + * %state_home% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_APPDATA + * %state_home2% Linux: $XDG_CONFIG_HOME. Windows: CSIDL_MYDOCUMENTS + * %state_home3% Linux: $XDG_CONFIG_HOME. Windows: home + * %data_home% Linux: $XDG_DATA_HOME. Windows: CSIDL_APPDATA + * %data_home2% Linux: $XDG_DATA_HOME. Windows: CSIDL_MYDOCUMENTS + * %data_home3% Linux: $XDG_DATA_HOME. Windows: home * * * @param path diff --git a/nui/src/nui/backend/filesystem/special_paths.cpp b/nui/src/nui/backend/filesystem/special_paths.cpp index d10591f5..d5851f02 100644 --- a/nui/src/nui/backend/filesystem/special_paths.cpp +++ b/nui/src/nui/backend/filesystem/special_paths.cpp @@ -31,6 +31,10 @@ namespace Nui { return getSpecialPath(CSIDL_APPDATA); } + std::filesystem::path getDocuments() + { + return getSpecialPath(CSIDL_MYDOCUMENTS); + } std::filesystem::path getLocalAppData() { return getSpecialPath(CSIDL_LOCAL_APPDATA); @@ -67,6 +71,27 @@ namespace Nui { return "/tmp"; } + std::filesystem::path getXdgConfigHome() + { + auto const* xdgConfigHome = getenv("XDG_CONFIG_HOME"); + if (xdgConfigHome != nullptr) + return {xdgConfigHome}; + return getHome() / ".config"; + } + std::filesystem::path getXdgStateHome() + { + auto const* xdgStateHome = getenv("XDG_STATE_HOME"); + if (xdgStateHome != nullptr) + return {xdgStateHome}; + return getHome() / ".local/state"; + } + std::filesystem::path getXdgDataHome() + { + auto const* xdgDataHome = getenv("XDG_DATA_HOME"); + if (xdgDataHome != nullptr) + return {xdgDataHome}; + return getHome() / ".local/share"; + } #endif } @@ -106,6 +131,45 @@ namespace Nui return path; if (tryAndMap("%temp%", getTemp)) return path; +#ifdef _WIN32 + if (tryAndMap("%config_home%", getAppData)) + return path; + if (tryAndMap("%config_home2%", getDocuments)) + return path; + if (tryAndMap("%config_home3%", getHome)) + return path; + if (tryAndMap("%state_home%", getAppData)) + return path; + if (tryAndMap("%state_home2%", getDocuments)) + return path; + if (tryAndMap("%state_home3%", getHome)) + return path; + if (tryAndMap("%data_home%", getAppData)) + return path; + if (tryAndMap("%data_home2%", getDocuments)) + return path; + if (tryAndMap("%data_home3%", getHome)) + return path; +#else + if (tryAndMap("%config_home%", getXdgConfigHome)) + return path; + if (tryAndMap("%config_home2%", getXdgConfigHome)) + return path; + if (tryAndMap("%config_home3%", getXdgConfigHome)) + return path; + if (tryAndMap("%state_home%", getXdgStateHome)) + return path; + if (tryAndMap("%state_home2%", getXdgStateHome)) + return path; + if (tryAndMap("%state_home3%", getXdgStateHome)) + return path; + if (tryAndMap("%data_home%", getXdgDataHome)) + return path; + if (tryAndMap("%data_home2%", getXdgDataHome)) + return path; + if (tryAndMap("%data_home3%", getXdgDataHome)) + return path; +#endif return path; } From d23208b99b580f466acba7e53ff95d4e16c1f175 Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 21 Jul 2024 05:26:05 +0200 Subject: [PATCH 07/20] Fixed range renderer reset and weak_ptr handling. --- .../nui/event_system/observed_value.hpp | 6 + .../nui/event_system/range_event_context.hpp | 4 + .../frontend/elements/impl/html_element.hpp | 8 +- .../frontend/elements/impl/range_renderer.hpp | 7 +- .../frontend/elements/impl/range_renderer.tpp | 141 +++++++++++++----- nui/test/nui/components/test_select.hpp | 8 +- 6 files changed, 127 insertions(+), 47 deletions(-) diff --git a/nui/include/nui/event_system/observed_value.hpp b/nui/include/nui/event_system/observed_value.hpp index d970e48a..5b380598 100644 --- a/nui/include/nui/event_system/observed_value.hpp +++ b/nui/include/nui/event_system/observed_value.hpp @@ -1428,27 +1428,33 @@ namespace Nui struct ObservedAddMutableReference { using type = T&; + using raw = T; }; template <> struct ObservedAddMutableReference { using type = void; + using raw = void; }; template struct ObservedAddMutableReference>> { using type = std::weak_ptr>; + using raw = Observed; }; template struct ObservedAddMutableReference>> { using type = std::weak_ptr>; + using raw = Observed; }; template using ObservedAddReference_t = typename ObservedAddReference>::type; template using ObservedAddMutableReference_t = typename ObservedAddMutableReference>::type; + template + using ObservedAddMutableReference_raw = typename ObservedAddMutableReference>::raw; } template diff --git a/nui/include/nui/event_system/range_event_context.hpp b/nui/include/nui/event_system/range_event_context.hpp index 76cdca5f..af79a06e 100644 --- a/nui/include/nui/event_system/range_event_context.hpp +++ b/nui/include/nui/event_system/range_event_context.hpp @@ -261,6 +261,10 @@ namespace Nui return insertModificationRange( static_cast(elementCount), static_cast(low), static_cast(high), type); } + void reset() + { + reset(0, false); + } void reset(long dataSize, bool requireFullRangeUpdate) { modificationRanges_.clear(); diff --git a/nui/include/nui/frontend/elements/impl/html_element.hpp b/nui/include/nui/frontend/elements/impl/html_element.hpp index 8aaf4a1b..bd4a5ec1 100644 --- a/nui/include/nui/frontend/elements/impl/html_element.hpp +++ b/nui/include/nui/frontend/elements/impl/html_element.hpp @@ -161,10 +161,10 @@ namespace Nui auto rangeRender(RangeType&& valueRange, GeneratorT&& elementRenderer) && { return [self = this->clone(), - rangeRenderer = - std::make_shared>( - std::move(valueRange).underlying(), std::forward(elementRenderer))]( - auto& parentElement, Renderer const& gen) { + rangeRenderer = std::make_shared< + Detail::RangeRenderer, GeneratorT, RangeType::isRandomAccess>>( + std::move(valueRange).underlying(), std::forward(elementRenderer))]( + auto& parentElement, Renderer const& gen) mutable { if (gen.type == RendererType::Inplace) throw std::runtime_error("fragments are not supported for range generators"); diff --git a/nui/include/nui/frontend/elements/impl/range_renderer.hpp b/nui/include/nui/frontend/elements/impl/range_renderer.hpp index a0c395e9..9fa01388 100644 --- a/nui/include/nui/frontend/elements/impl/range_renderer.hpp +++ b/nui/include/nui/frontend/elements/impl/range_renderer.hpp @@ -1,10 +1,13 @@ #pragma once -#include -#include +#include +#include #include +#include +#include #include +#include namespace Nui::Detail { diff --git a/nui/include/nui/frontend/elements/impl/range_renderer.tpp b/nui/include/nui/frontend/elements/impl/range_renderer.tpp index 9a413702..ed8ff4cf 100644 --- a/nui/include/nui/frontend/elements/impl/range_renderer.tpp +++ b/nui/include/nui/frontend/elements/impl/range_renderer.tpp @@ -6,15 +6,32 @@ namespace Nui::Detail KeepRange = true, }; + template + struct HoldToken + { + // FIXME: Remove this, when getValueRange overload is not instantiated for non weak observed types + std::shared_ptr locked; + }; + + template + struct HoldToken> + { + std::shared_ptr locked; + }; + + template + using CommonHoldToken = ::Nui::Detail::HoldToken>; + template class BasicObservedRenderer { public: using ObservedType = typename RangeT::ObservedType; + using HoldToken = CommonHoldToken; template - BasicObservedRenderer(ObservedType& observed, Generator&& elementRenderer) - : valueRange_{observed} + BasicObservedRenderer(ObservedAddMutableReference_t&& observed, Generator&& elementRenderer) + : valueRange_{std::forward>(observed)} , elementRenderer_{std::forward(elementRenderer)} {} virtual ~BasicObservedRenderer() = default; @@ -23,25 +40,46 @@ namespace Nui::Detail BasicObservedRenderer& operator=(BasicObservedRenderer const&) = delete; BasicObservedRenderer& operator=(BasicObservedRenderer&&) = delete; - bool fullRangeUpdate(auto& parent) const + auto* getValueRange(HoldToken& holder) + { + return Nui::overloaded{ + [](::Nui::IsObserved auto& valueRange) { + return &valueRange; + }, + [&holder](::Nui::IsWeakObserved auto& valueRange) { + if (holder.locked = valueRange.lock(); holder.locked) + return holder.locked.get(); + return nullptr; + }, + }(valueRange_); + } + + bool fullRangeUpdate(auto& parent) { - if (valueRange_.rangeContext().isFullRangeUpdate()) + auto valueRangeHolder = HoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return false; + + if (valueRange->rangeContext().isFullRangeUpdate()) { parent->clearChildren(); long long counter = 0; - for (auto& element : valueRange_.value()) + for (auto& element : valueRange->value()) elementRenderer_(counter++, element)(*parent, Renderer{.type = RendererType::Append}); return true; } return false; } - virtual bool updateChildren() const = 0; + virtual bool updateChildren() = 0; protected: - ObservedType& valueRange_; GeneratorT elementRenderer_; std::weak_ptr weakMaterialized_; + + private: + ObservedAddMutableReference_t valueRange_; }; template @@ -52,25 +90,35 @@ namespace Nui::Detail public: using BasicObservedRenderer::BasicObservedRenderer; using BasicObservedRenderer::weakMaterialized_; - using BasicObservedRenderer::valueRange_; using BasicObservedRenderer::elementRenderer_; using BasicObservedRenderer::fullRangeUpdate; + using BasicObservedRenderer::getValueRange; - void insertions(auto& parent) const + void insertions(auto& parent) { - if (const auto insertInterval = valueRange_.rangeContext().insertInterval(); insertInterval) + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return; + + if (const auto insertInterval = valueRange->rangeContext().insertInterval(); insertInterval) { for (auto i = insertInterval->low(); i <= insertInterval->high(); ++i) { - elementRenderer_(i, valueRange_.value()[static_cast(i)])( + elementRenderer_(i, valueRange->value()[static_cast(i)])( *parent, Renderer{.type = RendererType::Insert, .metadata = static_cast(i)}); } } } - void updates(auto& parent) const + void modifications(auto& parent) { - for (auto const& range : valueRange_.rangeContext()) + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange == nullptr) + return; + + for (auto const& range : valueRange->rangeContext()) { switch (range.type()) { @@ -82,7 +130,7 @@ namespace Nui::Detail { for (auto i = range.low(), high = range.high(); i <= high; ++i) { - elementRenderer_(i, valueRange_.value()[static_cast(i)])( + elementRenderer_(i, valueRange->value()[static_cast(i)])( *(*parent)[static_cast(i)], Renderer{.type = RendererType::Replace}); } break; @@ -93,18 +141,26 @@ namespace Nui::Detail } } - bool updateChildren() const override + bool updateChildren() override { auto parent = weakMaterialized_.lock(); if (!parent) return InvalidateRange; + Nui::ScopeExit onExit{[this] { + auto valueRangeHolder = ::Nui::Detail::HoldToken< + std::decay_t>>{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange) + valueRange->rangeContext().reset(); + }}; + // Regenerate all elements if necessary: if (fullRangeUpdate(parent)) return KeepRange; insertions(parent); - updates(parent); + modifications(parent); return KeepRange; } @@ -112,15 +168,21 @@ namespace Nui::Detail void operator()(auto& materialized) { weakMaterialized_ = materialized; - valueRange_.attachEvent(Nui::globalEventContext.registerEvent(Event{ - [self = this->shared_from_this()](int) -> bool { - return self->updateChildren(); - }, - [this /* fine because other function holds this */]() { - return !weakMaterialized_.expired(); - }, - })); - updateChildren(); + + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange) + { + valueRange->attachEvent(Nui::globalEventContext.registerEvent(Event{ + [self = this->shared_from_this()](int) -> bool { + return self->updateChildren(); + }, + [this /* fine because other function holds this */]() { + return !weakMaterialized_.expired(); + }, + })); + updateChildren(); + } } }; @@ -132,11 +194,11 @@ namespace Nui::Detail public: using BasicObservedRenderer::BasicObservedRenderer; using BasicObservedRenderer::weakMaterialized_; - using BasicObservedRenderer::valueRange_; using BasicObservedRenderer::elementRenderer_; using BasicObservedRenderer::fullRangeUpdate; + using BasicObservedRenderer::getValueRange; - bool updateChildren() const override + bool updateChildren() override { auto parent = weakMaterialized_.lock(); if (!parent) @@ -149,15 +211,20 @@ namespace Nui::Detail void operator()(auto& materialized) { weakMaterialized_ = materialized; - valueRange_.attachEvent(Nui::globalEventContext.registerEvent(Event{ - [self = this->shared_from_this()](int) -> bool { - return self->updateChildren(); - }, - [this /* fine because other function holds this */]() { - return !weakMaterialized_.expired(); - }, - })); - updateChildren(); + auto valueRangeHolder = CommonHoldToken{}; + auto* valueRange = getValueRange(valueRangeHolder); + if (valueRange) + { + valueRange->attachEvent(Nui::globalEventContext.registerEvent(Event{ + [self = this->shared_from_this()](int) -> bool { + return self->updateChildren(); + }, + [this /* fine because other function holds this */]() { + return !weakMaterialized_.expired(); + }, + })); + updateChildren(); + } } }; @@ -174,7 +241,7 @@ namespace Nui::Detail , elementRenderer_{std::forward(elementRenderer)} {} - bool updateChildren() const + bool updateChildren() { auto materialized = weakMaterialized_.lock(); if (!materialized) diff --git a/nui/test/nui/components/test_select.hpp b/nui/test/nui/components/test_select.hpp index 4eb64851..25ff9640 100644 --- a/nui/test/nui/components/test_select.hpp +++ b/nui/test/nui/components/test_select.hpp @@ -27,7 +27,7 @@ namespace Nui::Tests model_.value().emplace_back(Components::SelectOptions{.label = "foo", .value = 7}); model_.value().emplace_back(Components::SelectOptions{.label = "bar", .value = 8}); - const auto select = Components::Select({ + auto select = Components::Select({ .model = model_, }); @@ -47,7 +47,7 @@ namespace Nui::Tests model_.value().emplace_back(Components::SelectOptions{.label = "foo", .value = 7}); model_.value().emplace_back(Components::SelectOptions{.label = "bar", .value = 8}); - const auto select = Components::Select({.model = model_, .preSelectedIndex = 1}); + auto select = Components::Select({.model = model_, .preSelectedIndex = 1}); render(select); @@ -60,7 +60,7 @@ namespace Nui::Tests { using namespace Nui::Attributes; - const auto select = Components::Select({ + auto select = Components::Select({ .model = model_, .selectAttributes = { @@ -82,7 +82,7 @@ namespace Nui::Tests model_.value().emplace_back(Components::SelectOptions{.label = "foo", .value = 7}); bool called = false; - const auto select = Components::Select({ + auto select = Components::Select({ .model = model_, .onSelect = [&called](long long index, Components::SelectOptions const& opt) { From c2497accd6c9bd5b922123b9ad096bb511bde8ab Mon Sep 17 00:00:00 2001 From: Tim Ebbeke Date: Sun, 21 Jul 2024 05:27:48 +0200 Subject: [PATCH 08/20] Added tests for fix of missing optimization. --- nui/test/nui/CMakeLists.txt | 1 + nui/test/nui/emscripten_mock/emscripten/val.h | 2 +- nui/test/nui/engine/array.cpp | 12 ++ nui/test/nui/engine/array.hpp | 13 +- nui/test/nui/engine/document.cpp | 92 ++++++---- nui/test/nui/test_ranges.hpp | 161 +++++++++++++++++- 6 files changed, 245 insertions(+), 36 deletions(-) diff --git a/nui/test/nui/CMakeLists.txt b/nui/test/nui/CMakeLists.txt index 4e10955e..fe3d93be 100644 --- a/nui/test/nui/CMakeLists.txt +++ b/nui/test/nui/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(nui-tests target_link_libraries(nui-tests PRIVATE nui-frontend-mocked gtest + gmock ) gtest_discover_tests(nui-tests) diff --git a/nui/test/nui/emscripten_mock/emscripten/val.h b/nui/test/nui/emscripten_mock/emscripten/val.h index 14e6f21c..6b9ff2ba 100644 --- a/nui/test/nui/emscripten_mock/emscripten/val.h +++ b/nui/test/nui/emscripten_mock/emscripten/val.h @@ -197,7 +197,7 @@ namespace emscripten } template - Ret call(char const* name, List&&... args) + Ret call(char const* name, List&&... args) const { #ifdef NUI_TEST_DEBUG_PRINT std::cout << "val::call<" << boost::typeindex::type_id().pretty_name() << "(" diff --git a/nui/test/nui/engine/array.cpp b/nui/test/nui/engine/array.cpp index 09ed7530..782d353d 100644 --- a/nui/test/nui/engine/array.cpp +++ b/nui/test/nui/engine/array.cpp @@ -26,6 +26,18 @@ namespace Nui::Tests::Engine return allValues[*values_[index]]; } + void Array::insert(const_iterator it, std::shared_ptr const& reference) + { + values_.insert(it, reference); + updateArrayObject(); + } + + void Array::insert(iterator it, std::shared_ptr const& reference) + { + values_.insert(it, reference); + updateArrayObject(); + } + std::shared_ptr Array::reference(std::size_t index) const { return values_[index]; diff --git a/nui/test/nui/engine/array.hpp b/nui/test/nui/engine/array.hpp index b9c1dbd9..0b8ad850 100644 --- a/nui/test/nui/engine/array.hpp +++ b/nui/test/nui/engine/array.hpp @@ -16,6 +16,9 @@ namespace Nui::Tests::Engine class Array { public: + using const_iterator = boost::container::stable_vector>::const_iterator; + using iterator = boost::container::stable_vector>::iterator; + Array(); Array(const Array&); Array(Array&&); @@ -26,19 +29,21 @@ namespace Nui::Tests::Engine const Value& operator[](std::size_t index) const; std::shared_ptr reference(std::size_t index) const; - boost::container::stable_vector>::const_iterator begin() const; - boost::container::stable_vector>::const_iterator end() const; + const_iterator begin() const; + const_iterator end() const; std::size_t size() const; bool empty() const; - std::shared_ptr push_back(Value const& value); std::shared_ptr push_back(std::shared_ptr const& reference); void clearUndefinedAndNull(); void erase(std::size_t index); - void erase(boost::container::stable_vector>::const_iterator it); + void erase(const_iterator it); + + void insert(const_iterator it, std::shared_ptr const& reference); + void insert(iterator it, std::shared_ptr const& reference); Object const& asObject() const; diff --git a/nui/test/nui/engine/document.cpp b/nui/test/nui/engine/document.cpp index 7a306647..557e4c13 100644 --- a/nui/test/nui/engine/document.cpp +++ b/nui/test/nui/engine/document.cpp @@ -55,39 +55,73 @@ namespace Nui::Tests::Engine { auto elem = createBasicElement(tag); elem.set("nodeType", int{1}); - elem.set("appendChild", Function{[self = elem](Nui::val value) -> Nui::val { - if (value.hasOwnProperty("parentNode")) - value["parentNode"].call("removeChild", value); - value.set("parentNode", self); - self["childNodes"].template as().push_back(value.handle()); - return self["children"].template as().push_back(value.handle()); - }}); - elem.set("setAttribute", Function{[self = elem](Nui::val name, Nui::val value) -> Nui::val { - if (!self.template as().has("attributes")) - self.set("attributes", createValue(Object{})); + elem.set( + "appendChild", + Function{ + [self = elem](Nui::val value) -> Nui::val { + if (value.hasOwnProperty("parentNode")) + value["parentNode"].call("removeChild", value); + value.set("parentNode", self); + self["childNodes"].template as().push_back(value.handle()); + return self["children"].template as().push_back(value.handle()); + }, + }); + elem.set( + "setAttribute", + Function{ + [self = elem](Nui::val name, Nui::val value) -> Nui::val { + if (!self.template as().has("attributes")) + self.set("attributes", createValue(Object{})); - self["attributes"].set(name.template as(), *value.handle()); + self["attributes"].set(name.template as(), *value.handle()); - return Nui::val::undefined(); - }}); - elem.set("removeAttribute", Function{[self = elem](Nui::val name) -> Nui::val { - if (!self.template as().has("attributes")) - return Nui::val::undefined(); + return Nui::val::undefined(); + }, + }); + elem.set( + "removeAttribute", + Function{ + [self = elem](Nui::val name) -> Nui::val { + if (!self.template as().has("attributes")) + return Nui::val::undefined(); - self["attributes"].delete_(name.template as()); + self["attributes"].delete_(name.template as()); - return Nui::val::undefined(); - }}); - elem.set("removeChild", Function{[self = elem](Nui::val value) -> Nui::val { - auto eraseIt = [&](auto& container) { - auto it = std::find(container.begin(), container.end(), value.handle()); - if (it != container.end()) - container.erase(it); - }; - eraseIt(value["children"].template as()); - eraseIt(value["childNodes"].template as()); - return Nui::val::undefined(); - }}); + return Nui::val::undefined(); + }, + }); + elem.set( + "removeChild", + Function{ + [self = elem](Nui::val value) -> Nui::val { + auto eraseIt = [&](auto& container) { + auto it = std::find(container.begin(), container.end(), value.handle()); + if (it != container.end()) + container.erase(it); + }; + eraseIt(value["children"].template as()); + eraseIt(value["childNodes"].template as()); + return Nui::val::undefined(); + }, + }); + elem.set( + "insertBefore", + Function{ + [self = elem](Nui::val value, Nui::val referenceNode) -> Nui::val { + if (referenceNode.isNull()) + return self.call("appendChild", value); + + if (value.hasOwnProperty("parentNode")) + value["parentNode"].call("removeChild", value); + + value.set("parentNode", self); + auto& children = self["children"].template as(); + auto it = std::find(children.begin(), children.end(), referenceNode.handle()); + if (it != children.end()) + children.insert(it, value.handle()); + return Nui::val::undefined(); + }, + }); return elem; } diff --git a/nui/test/nui/test_ranges.hpp b/nui/test/nui/test_ranges.hpp index 13b4f813..11893530 100644 --- a/nui/test/nui/test_ranges.hpp +++ b/nui/test/nui/test_ranges.hpp @@ -3,6 +3,7 @@ #pragma once #include +#include #include "common_test_fixture.hpp" #include "engine/global_object.hpp" @@ -25,7 +26,7 @@ namespace Nui::Tests { protected: template