Skip to content

Commit

Permalink
Allow having bindings referencing const Properties
Browse files Browse the repository at this point in the history
  • Loading branch information
phyBrackets committed Jun 2, 2024
1 parent 7a2b6d2 commit ec7f588
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 0 deletions.
36 changes: 36 additions & 0 deletions src/kdbindings/binding.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,24 @@ inline std::unique_ptr<Binding<T, EvaluatorT>> makeBinding(EvaluatorT &evaluator
return std::make_unique<Binding<T, EvaluatorT>>(Private::makeNode(property), evaluator);
}

/**
* @brief Creates a binding from a const property using a specified evaluator.
*
* @tparam T The type of the value that the Binding expression evaluates to.
* @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding.
* @param evaluator The evaluator that is used to evaluate the Binding.
* @param property The const Property to create a Binding from.
* @return std::unique_ptr<Binding<T, EvaluatorT>> A new Binding that is powered by the evaluator.
*
* *Note: Using a const Property ensures that the source cannot be modified through this binding,
* maintaining data integrity and supporting scenarios where data should only be observed, not altered.*
*/
template<typename T, typename EvaluatorT>
inline std::unique_ptr<Binding<T, EvaluatorT>> makeBinding(EvaluatorT &evaluator, const Property<T> &property)
{
return std::make_unique<Binding<T, EvaluatorT>>(Private::makeNode(property), evaluator);
}

/**
* @brief Helper function to create a Binding from a root Node.
*
Expand Down Expand Up @@ -227,6 +245,24 @@ inline std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> makeBinding(Proper
return std::make_unique<Binding<T, ImmediateBindingEvaluator>>(Private::makeNode(property));
}

/**
* @brief Creates an immediate mode binding from a const property.
*
* @tparam T The type of the value that the Binding expression evaluates to.
* @param property The const Property to create a Binding from.
* @return std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> A new Binding that is powered by an
* immediate mode evaluator, ensuring quick updates to changes.
*
* *Note: This binding type is especially suited for scenarios where you need to reflect
* changes in a property to some dependent components without the need to alter the
* source property itself.*
*/
template<typename T>
inline std::unique_ptr<Binding<T, ImmediateBindingEvaluator>> makeBinding(const Property<T> &property)
{
return std::make_unique<Binding<T, ImmediateBindingEvaluator>>(Private::makeNode(property));
}

/**
* @brief Helper function to create an immediate mode Binding from a root Node.
*
Expand Down
11 changes: 11 additions & 0 deletions src/kdbindings/make_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ struct bindable_value_type_<Property<T>> {
using type = T;
};

template<typename T>
struct bindable_value_type_<const Property<T>> {
using type = T;
};

template<typename T>
struct bindable_value_type_<NodeInterface<T>> {
using type = T;
Expand Down Expand Up @@ -69,6 +74,12 @@ inline Node<T> makeNode(Property<T> &property)
return Node<T>(std::make_unique<PropertyNode<T>>(property));
}

template<typename T>
inline Node<T> makeNode(const Property<T> &property)
{
return Node<T>(std::make_unique<ConstPropertyNode<T>>(property));
}

template<typename T>
inline Node<T> makeNode(Node<T> &&node)
{
Expand Down
58 changes: 58 additions & 0 deletions src/kdbindings/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,64 @@ class PropertyNode : public NodeInterface<PropertyType>
mutable bool m_dirty;
};

template<typename PropertyType>
class ConstPropertyNode : public NodeInterface<PropertyType>
{
public:
explicit ConstPropertyNode(const Property<PropertyType> &property)
: m_parent(nullptr), m_dirty(false), m_property(&property)
{
m_valueChangedHandle = property.valueChanged().connect(&ConstPropertyNode<PropertyType>::markDirty, this);
m_destroyedHandle = property.destroyed().connect(&ConstPropertyNode<PropertyType>::propertyDestroyed, this);
}

// Copy constructor
ConstPropertyNode(const ConstPropertyNode<PropertyType> &other)
: m_parent(nullptr), m_dirty(false), m_property(other.m_property)
{
if (m_property) {
m_valueChangedHandle = m_property->valueChanged().connect(&ConstPropertyNode<PropertyType>::markDirty, this);
m_destroyedHandle = m_property->destroyed().connect(&ConstPropertyNode<PropertyType>::propertyDestroyed, this);
}
}

// Move constructor is deleted to prevent moving a const reference
ConstPropertyNode(ConstPropertyNode<PropertyType> &&) = delete;

virtual ~ConstPropertyNode()
{
m_valueChangedHandle.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 propertyDestroyed()
{
m_property = nullptr;
}

protected:
Dirtyable **parentVariable() override { return &m_parent; }
const bool *dirtyVariable() const override { return &m_dirty; }

private:
const Property<PropertyType> *m_property;
ConnectionHandle m_valueChangedHandle;
ConnectionHandle m_destroyedHandle;

Dirtyable *m_parent;
mutable bool m_dirty;
};

template<typename ResultType, typename Operator, typename... Ts>
class OperatorNode : public NodeInterface<ResultType>
{
Expand Down
35 changes: 35 additions & 0 deletions tests/binding/tst_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,41 @@ TEST_CASE("Create property bindings using helper function")
}
}

TEST_CASE("Binding with const Property")
{
SUBCASE("A binding created from a const property reflects its value")
{
const Property<int> constSource(5);
auto bound = makeBoundProperty(constSource);

REQUIRE(bound.get() == 5); // Ensure the bound property reflects the const source
}

SUBCASE("A binding to a const property does not allow modification through set")
{
const Property<int> constSource(5);
auto bound = makeBoundProperty(constSource);

REQUIRE_THROWS_AS(bound.set(10), ReadOnlyProperty);
}

SUBCASE("Updates to const property are reflected in the binding")
{
Property<int> source(5);
const Property<int> &constRefSource = source; // Create a const reference to a non-const Property
auto bound = makeBoundProperty(constRefSource);

REQUIRE(bound.get() == 5);

bool called = false;
bound.valueChanged().connect([&called]() { called = true; });

source = 10; // Change the original non-const property
REQUIRE(called);
REQUIRE(bound.get() == 10);
}
}

TEST_CASE("Binding reassignment")
{
SUBCASE("A binding can not be override by a constant value")
Expand Down

0 comments on commit ec7f588

Please sign in to comment.