2026-04-06 00:20:51 -05:00

315 lines
7.7 KiB
C++

/*
This file is part of KDBindings.
SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
Author: Sean Harmer <sean.harmer@kdab.com>
SPDX-License-Identifier: MIT
Contact KDAB at <info@kdab.com> for commercial licensing options.
*/
#pragma once
#include <kdbindings/property.h>
#include <kdbindings/signal.h>
#include <functional>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
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<bool *>(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<typename ResultType>
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<typename ResultType>
class Node
{
public:
Node(std::unique_ptr<NodeInterface<ResultType>> &&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<NodeInterface<ResultType>> m_interface;
};
template<typename T>
class ConstantNode : public NodeInterface<T>
{
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<typename PropertyType>
class PropertyNode : public NodeInterface<PropertyType>
{
public:
explicit PropertyNode(const Property<PropertyType> &property)
: m_parent(nullptr), m_dirty(false)
{
setProperty(property);
}
// PropertyNodes cannot be moved
PropertyNode(PropertyNode<PropertyType> &&) = delete;
PropertyNode(const PropertyNode<PropertyType> &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<PropertyType> &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<PropertyType> &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<PropertyType> &newProp) { this->propertyMoved(newProp); });
m_destroyedHandle = m_property->destroyed().connect([this]() { this->propertyDestroyed(); });
}
const Property<PropertyType> *m_property;
ConnectionHandle m_movedHandle;
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>
{
public:
// add another typename template for the Operator type, so
// it can be a universal reference.
template<typename Op>
explicit OperatorNode(Op &&op, Node<Ts> &&...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<decltype(m_op(std::declval<Ts>()...)), ResultType>,
"The result of the Operator must be convertible to the ReturnType of the Node");
setParents<0>();
}
template<std::size_t I>
auto setParents() -> std::enable_if_t<I == sizeof...(Ts)>
{
}
// The enable_if_t confuses clang-format into thinking the
// first "<" is a comparison, and not the second.
// clang-format off
template<std::size_t I>
auto setParents() -> std::enable_if_t<I < sizeof...(Ts)>
// clang-format on
{
std::get<I>(m_values).setParent(this);
setParents<I + 1>();
}
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<std::size_t... Is>
ResultType reevaluate_helper(std::index_sequence<Is...>) const
{
return m_op(std::get<Is>(m_values).evaluate()...);
}
ResultType reevaluate() const
{
m_dirty = false;
return reevaluate_helper(std::make_index_sequence<sizeof...(Ts)>());
}
Dirtyable *m_parent;
mutable bool m_dirty;
Operator m_op;
std::tuple<Node<Ts>...> 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<typename T>
struct is_node_helper : std::false_type {
};
template<typename T>
struct is_node_helper<Node<T>> : std::true_type {
};
template<typename T>
struct is_node : is_node_helper<T> {
};
} // namespace Private
} // namespace KDBindings