/* This file is part of KDBindings. SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company Author: Sean Harmer SPDX-License-Identifier: MIT Contact KDAB at for commercial licensing options. */ #pragma once #include #include #include #include #include #include #include namespace KDBindings { /** * @brief A PropertyDestroyedError is thrown whenever a binding is evaluated * that references a property that no longer exists. */ class PropertyDestroyedError : public std::runtime_error { public: PropertyDestroyedError() = delete; using std::runtime_error::runtime_error; }; namespace Private { class Dirtyable { public: virtual ~Dirtyable() = default; Dirtyable() = default; void setParent(Dirtyable *newParent) { auto **parentVar = parentVariable(); if (parentVar) { *parentVar = newParent; } } // overridden by Binding virtual void markDirty() { auto *dirtyVar = dirtyVariable(); if (dirtyVar) { if (*dirtyVar) { return; // We are already dirty, don't bother marking the whole tree again. } // we only want to have one override for dirtyVariable, // which is const, so we have to const cast here. *const_cast(dirtyVar) = true; } auto **parentVar = parentVariable(); if (parentVar && *parentVar) { (*parentVar)->markDirty(); } } bool isDirty() const { auto *dirtyVar = dirtyVariable(); return dirtyVar && *dirtyVar; } protected: virtual Dirtyable **parentVariable() = 0; virtual const bool *dirtyVariable() const = 0; }; template class NodeInterface : public Dirtyable { public: // Returns a reference, because we cache each evaluated value. // const, because it shouldn't modify the return value of the AST. // Requires mutable caches virtual const ResultType &evaluate() const = 0; protected: NodeInterface() = default; }; template class Node { public: Node(std::unique_ptr> &&nodeInterface) : m_interface(std::move(nodeInterface)) { } const ResultType &evaluate() const { return m_interface->evaluate(); } void setParent(Dirtyable *newParent) { m_interface->setParent(newParent); } bool isDirty() const { return m_interface->isDirty(); } private: std::unique_ptr> m_interface; }; template class ConstantNode : public NodeInterface { public: explicit ConstantNode(const T &value) : m_value{ value } { } const T &evaluate() const override { return m_value; } protected: // A constant can never be dirty, so it doesn't need to // know its parent, as it doesn't have to notify it. Dirtyable **parentVariable() override { return nullptr; } const bool *dirtyVariable() const override { return nullptr; } private: T m_value; }; template class PropertyNode : public NodeInterface { public: explicit PropertyNode(const Property &property) : m_parent(nullptr), m_dirty(false) { setProperty(property); } // PropertyNodes cannot be moved PropertyNode(PropertyNode &&) = delete; PropertyNode(const PropertyNode &other) : Dirtyable(other.isDirty()) { setProperty(*other.m_property); } virtual ~PropertyNode() { m_valueChangedHandle.disconnect(); m_movedHandle.disconnect(); m_destroyedHandle.disconnect(); } const PropertyType &evaluate() const override { if (!m_property) { throw PropertyDestroyedError("The Property this node refers to no longer exists!"); } m_dirty = false; return m_property->get(); } void propertyMoved(const Property &property) { if (&property != m_property) { m_property = &property; } else { // Another property was moved into the property this node refers to. // Therefore it will no longer update this Node. m_property = nullptr; } } void propertyDestroyed() { m_property = nullptr; } protected: Dirtyable **parentVariable() override { return &m_parent; } const bool *dirtyVariable() const override { return &m_dirty; } private: void setProperty(const Property &property) { m_property = &property; // Connect to all signals, even for const properties m_valueChangedHandle = m_property->valueChanged().connect([this]() { this->markDirty(); }); m_movedHandle = m_property->m_moved.connect([this](const Property &newProp) { this->propertyMoved(newProp); }); m_destroyedHandle = m_property->destroyed().connect([this]() { this->propertyDestroyed(); }); } const Property *m_property; ConnectionHandle m_movedHandle; ConnectionHandle m_valueChangedHandle; ConnectionHandle m_destroyedHandle; Dirtyable *m_parent; mutable bool m_dirty; }; template class OperatorNode : public NodeInterface { public: // add another typename template for the Operator type, so // it can be a universal reference. template explicit OperatorNode(Op &&op, Node &&...arguments) : m_parent{ nullptr }, m_dirty{ true /*dirty until reevaluated*/ }, m_op{ std::move(op) }, m_values{ std::move(arguments)... }, m_result(reevaluate()) { static_assert( std::is_convertible_v()...)), ResultType>, "The result of the Operator must be convertible to the ReturnType of the Node"); setParents<0>(); } template auto setParents() -> std::enable_if_t { } // The enable_if_t confuses clang-format into thinking the // first "<" is a comparison, and not the second. // clang-format off template auto setParents() -> std::enable_if_t // clang-format on { std::get(m_values).setParent(this); setParents(); } virtual ~OperatorNode() = default; const ResultType &evaluate() const override { if (Dirtyable::isDirty()) { m_result = reevaluate(); } return m_result; } protected: Dirtyable **parentVariable() override { return &m_parent; } const bool *dirtyVariable() const override { return &m_dirty; } private: template ResultType reevaluate_helper(std::index_sequence) const { return m_op(std::get(m_values).evaluate()...); } ResultType reevaluate() const { m_dirty = false; return reevaluate_helper(std::make_index_sequence()); } Dirtyable *m_parent; mutable bool m_dirty; Operator m_op; std::tuple...> m_values; // Note: it is important that m_result is evaluated last! // Otherwise the call to reevaluate in the constructor will fail. mutable ResultType m_result; }; template struct is_node_helper : std::false_type { }; template struct is_node_helper> : std::true_type { }; template struct is_node : is_node_helper { }; } // namespace Private } // namespace KDBindings