From 1e09caaa2980fe901453b4b90985967a51157887 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Mon, 19 Apr 2021 14:00:47 -0700 Subject: Split workflow into multiple files, fix unity build --- 3rdparty/imgui-node-editor/CMakeLists.txt | 1 + 3rdparty/imgui/CMakeLists.txt | 1 + 3rdparty/implot/CMakeLists.txt | 1 + CMakeLists.txt | 2 +- core/CMakeLists.txt | 39 +- core/src/Model/EvaluatedValue.cpp | 84 ---- core/src/Model/EvaluatedValue.hpp | 71 --- core/src/Model/Workflow.cpp | 576 ----------------------- core/src/Model/Workflow.hpp | 211 --------- core/src/Model/Workflow/Evaluation.cpp | 183 +++++++ core/src/Model/Workflow/Evaluation.hpp | 57 +++ core/src/Model/Workflow/Nodes/DocumentNodes.cpp | 15 + core/src/Model/Workflow/Nodes/DocumentNodes.hpp | 12 + core/src/Model/Workflow/Nodes/NumericNodes.cpp | 86 ++++ core/src/Model/Workflow/Nodes/NumericNodes.hpp | 41 ++ core/src/Model/Workflow/Nodes/TextNodes.cpp | 215 +++++++++ core/src/Model/Workflow/Nodes/TextNodes.hpp | 50 ++ core/src/Model/Workflow/Nodes/UserInputNodes.cpp | 26 + core/src/Model/Workflow/Nodes/UserInputNodes.hpp | 21 + core/src/Model/Workflow/Nodes/fwd.hpp | 15 + core/src/Model/Workflow/Value.cpp | 9 + core/src/Model/Workflow/Value.hpp | 28 ++ core/src/Model/Workflow/Values/BasicValues.cpp | 76 +++ core/src/Model/Workflow/Values/BasicValues.hpp | 46 ++ core/src/Model/Workflow/Values/fwd.hpp | 6 + core/src/Model/Workflow/Workflow.cpp | 396 ++++++++++++++++ core/src/Model/Workflow/Workflow.hpp | 163 +++++++ core/src/Model/Workflow/fwd.hpp | 14 + core/src/Model/WorkflowNodes.cpp | 312 ------------ core/src/Model/WorkflowNodes.hpp | 101 ---- core/src/Model/fwd.hpp | 21 +- core/src/UI/UI_Workflows.cpp | 96 +++- 32 files changed, 1568 insertions(+), 1407 deletions(-) delete mode 100644 core/src/Model/EvaluatedValue.cpp delete mode 100644 core/src/Model/EvaluatedValue.hpp delete mode 100644 core/src/Model/Workflow.cpp delete mode 100644 core/src/Model/Workflow.hpp create mode 100644 core/src/Model/Workflow/Evaluation.cpp create mode 100644 core/src/Model/Workflow/Evaluation.hpp create mode 100644 core/src/Model/Workflow/Nodes/DocumentNodes.cpp create mode 100644 core/src/Model/Workflow/Nodes/DocumentNodes.hpp create mode 100644 core/src/Model/Workflow/Nodes/NumericNodes.cpp create mode 100644 core/src/Model/Workflow/Nodes/NumericNodes.hpp create mode 100644 core/src/Model/Workflow/Nodes/TextNodes.cpp create mode 100644 core/src/Model/Workflow/Nodes/TextNodes.hpp create mode 100644 core/src/Model/Workflow/Nodes/UserInputNodes.cpp create mode 100644 core/src/Model/Workflow/Nodes/UserInputNodes.hpp create mode 100644 core/src/Model/Workflow/Nodes/fwd.hpp create mode 100644 core/src/Model/Workflow/Value.cpp create mode 100644 core/src/Model/Workflow/Value.hpp create mode 100644 core/src/Model/Workflow/Values/BasicValues.cpp create mode 100644 core/src/Model/Workflow/Values/BasicValues.hpp create mode 100644 core/src/Model/Workflow/Values/fwd.hpp create mode 100644 core/src/Model/Workflow/Workflow.cpp create mode 100644 core/src/Model/Workflow/Workflow.hpp create mode 100644 core/src/Model/Workflow/fwd.hpp delete mode 100644 core/src/Model/WorkflowNodes.cpp delete mode 100644 core/src/Model/WorkflowNodes.hpp diff --git a/3rdparty/imgui-node-editor/CMakeLists.txt b/3rdparty/imgui-node-editor/CMakeLists.txt index 2730a8e..47199dc 100644 --- a/3rdparty/imgui-node-editor/CMakeLists.txt +++ b/3rdparty/imgui-node-editor/CMakeLists.txt @@ -1,6 +1,7 @@ file(GLOB IMGUI_NODE_EDITOR_SOURCES *.cpp) add_library(imgui-node-editor ${IMGUI_NODE_EDITOR_SOURCES}) +set_target_properties(imgui-node-editor PROPERTIES UNITY_BUILD OFF) target_include_directories(imgui-node-editor PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty/imgui-node-editor ${CMAKE_SOURCE_DIR}/3rdparty/imgui diff --git a/3rdparty/imgui/CMakeLists.txt b/3rdparty/imgui/CMakeLists.txt index de80f9a..bf09c56 100644 --- a/3rdparty/imgui/CMakeLists.txt +++ b/3rdparty/imgui/CMakeLists.txt @@ -5,6 +5,7 @@ file(GLOB IMGUI_SOURCES *.cpp) # the build flags twice both in here and in core/CMakeLists.txt add_library(imgui ${IMGUI_SOURCES}) +set_target_properties(imgui PROPERTIES UNITY_BUILD OFF) target_include_directories(imgui PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty/imgui diff --git a/3rdparty/implot/CMakeLists.txt b/3rdparty/implot/CMakeLists.txt index 6b92b02..4fae8f2 100644 --- a/3rdparty/implot/CMakeLists.txt +++ b/3rdparty/implot/CMakeLists.txt @@ -1,6 +1,7 @@ file(GLOB IMPLOT_SOURCES *.cpp) add_library(implot ${IMPLOT_SOURCES}) +set_target_properties(implot PROPERTIES UNITY_BUILD OFF) target_include_directories(implot PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty/implot ${CMAKE_SOURCE_DIR}/3rdparty/imgui diff --git a/CMakeLists.txt b/CMakeLists.txt index 0517e4c..2fecd26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.18) project(Cplt LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index efa43ed..ccb5aeb 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -14,13 +14,13 @@ elseif(APPLE) option(BUILD_CORE_WITH_METAL_BACKEND ON) endif() -function(add_source_group GROUP_NAME) - set(${GROUP_NAME} ${ARGN} PARENT_SCOPE) +macro(add_source_group GROUP_NAME) + set(${GROUP_NAME} ${ARGN}) set_source_files_properties(${ARGN} - PROPERTIES + PROPERTIES UNITY_GROUP "${GROUP_NAME}" ) -endfunction() +endmacro() set(ENTRYPOINT_MODULE_SOURCES src/Entrypoint/main.cpp @@ -33,16 +33,30 @@ set(ENTRYPOINT_MODULE_SOURCES ) add_source_group(MODEL_MODULE_SOURCES - src/Model/EvaluatedValue.cpp src/Model/Filter.cpp src/Model/GlobalStates.cpp src/Model/Items.cpp src/Model/Project.cpp src/Model/TransactionsModel.cpp - src/Model/Workflow.cpp - src/Model/WorkflowNodes.cpp ) +add_source_group(MODEL_WORKFLOW_MODULE_SOURCES + src/Model/Workflow/Evaluation.cpp + src/Model/Workflow/Value.cpp + src/Model/Workflow/Workflow.cpp +) + +add_source_group(MODEL_WORKFLOW_NODES_MODULE_SOURCES + src/Model/Workflow/Nodes/DocumentNodes.cpp + src/Model/Workflow/Nodes/UserInputNodes.cpp + src/Model/Workflow/Nodes/NumericNodes.cpp + src/Model/Workflow/Nodes/TextNodes.cpp +) + +add_source_group(MODEL_WORKFLOW_VALUES_MODULE_SOURCES + src/Model/Workflow/Values/BasicValues.cpp + ) + add_source_group(UI_MODULE_SOURCES src/UI/Localization.cpp src/UI/States.cpp @@ -80,6 +94,9 @@ function(add_executable_variant TARGET_NAME) add_executable(${TARGET_NAME} ${ENTRYPOINT_MODULE_SOURCES} ${MODEL_MODULE_SOURCES} + ${MODEL_WORKFLOW_MODULE_SOURCES} + ${MODEL_WORKFLOW_NODES_MODULE_SOURCES} + ${MODEL_WORKFLOW_VALUES_MODULE_SOURCES} ${UI_MODULE_SOURCES} ${UTILS_MODULE_SOURCES} ${UTILS_DIALOG_MODULE_SOURCES} @@ -187,13 +204,9 @@ function(add_executable_variant TARGET_NAME) endif() endif() - if(BUILD_CORE_WITH_UNITY_BUILD) + if(CMAKE_UNITY_BUILD) message("CpltCore: - using unity build") - set_target_properties(${TARGET_NAME} - PROPERTIES - UNITY_BUILD ON - UNITY_BUILD_MODE GROUP - ) + set_target_properties(${TARGET_NAME} PROPERTIES UNITY_BUILD_MODE GROUP) else() message("CpltCore: - using regular build") endif() diff --git a/core/src/Model/EvaluatedValue.cpp b/core/src/Model/EvaluatedValue.cpp deleted file mode 100644 index 685d50f..0000000 --- a/core/src/Model/EvaluatedValue.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "EvaluatedValue.hpp" - -#include - -BaseValue::BaseValue(Kind kind) - : mKind{ kind } { -} - -BaseValue::Kind BaseValue::GetKind() const { - return mKind; -} - -bool NumericValue::IsInstance(const BaseValue* value) { - return value->GetKind() == KD_Numeric; -} - -NumericValue::NumericValue() - : BaseValue(BaseValue::KD_Numeric) { -} - -std::string NumericValue::GetString() const { - char buf[64]; - auto res = std::to_chars(buf, buf + std::size(buf), mValue); - if (res.ec == std::errc()) { - return std::string(buf, res.ptr); - } else { - // TODO larger buffer - return ""; - } -} - -int64_t NumericValue::GetInt() const { - return static_cast(mValue); -} - -double NumericValue::GetValue() const { - return mValue; -} - -void NumericValue::SetValue(double value) { - mValue = value; -} - -bool TextValue::IsInstance(const BaseValue* value) { - return value->GetKind() == KD_Text; -} - -TextValue::TextValue() - : BaseValue(BaseValue::KD_Text) { -} - -const std::string& TextValue::GetValue() const { - return mValue; -} - -void TextValue::SetValue(const std::string& value) { - mValue = value; -} - -bool DateTimeValue::IsInstance(const BaseValue* value) { - return value->GetKind() == KD_DateTime; -} - -DateTimeValue::DateTimeValue() - : BaseValue(BaseValue::KD_DateTime) { -} - -std::string DateTimeValue::GetString() const { - namespace chrono = std::chrono; - auto t = chrono::system_clock::to_time_t(mValue); - - char data[32]; - std::strftime(data, sizeof(data), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); - - return std::string(data); -} - -const std::chrono::time_point& DateTimeValue::GetValue() const { - return mValue; -} - -void DateTimeValue::SetValue(const std::chrono::time_point& value) { - mValue = value; -} diff --git a/core/src/Model/EvaluatedValue.hpp b/core/src/Model/EvaluatedValue.hpp deleted file mode 100644 index 880fd3e..0000000 --- a/core/src/Model/EvaluatedValue.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include -#include - -class BaseValue { -public: - enum Kind { - KD_Numeric, - KD_Text, - KD_DateTime, - - /// An unspecified type, otherwise known as "any" in some contexts. - KindInvalid, - KindCount = KindInvalid, - }; - -private: - Kind mKind; - -public: - BaseValue(Kind kind); - virtual ~BaseValue() = default; - - BaseValue(const BaseValue&) = delete; - BaseValue& operator=(const BaseValue&) = delete; - BaseValue(BaseValue&&) = default; - BaseValue& operator=(BaseValue&&) = default; - - Kind GetKind() const; -}; - -class NumericValue : public BaseValue { -private: - double mValue; - -public: - static bool IsInstance(const BaseValue* value); - NumericValue(); - - std::string GetString() const; - int64_t GetInt() const; - double GetValue() const; - void SetValue(double value); -}; - -class TextValue : public BaseValue { -private: - std::string mValue; - -public: - static bool IsInstance(const BaseValue* value); - TextValue(); - - const std::string& GetValue() const; - void SetValue(const std::string& value); -}; - -class DateTimeValue : public BaseValue { -private: - std::chrono::time_point mValue; - -public: - static bool IsInstance(const BaseValue* value); - DateTimeValue(); - - std::string GetString() const; - const std::chrono::time_point& GetValue() const; - void SetValue(const std::chrono::time_point& value); -}; diff --git a/core/src/Model/Workflow.cpp b/core/src/Model/Workflow.cpp deleted file mode 100644 index 7ca1b9e..0000000 --- a/core/src/Model/Workflow.cpp +++ /dev/null @@ -1,576 +0,0 @@ -#include "Workflow.hpp" - -#include -#include -#include -#include - -WorkflowConnection::WorkflowConnection() - : MultiConnections{} - , SingleConnection{ WorkflowNode::kInvalidId, -1 } - , ConnectionDirection{ OneToMany } { -} - -bool WorkflowConnection::IsValid() const { - return SingleConnection.Node != WorkflowNode::kInvalidId; -} - -std::span WorkflowConnection::GetSourcePoints() { - switch (ConnectionDirection) { - case ManyToOne: return MultiConnections; - case OneToMany: return { &SingleConnection, 1 }; - } -} - -std::span WorkflowConnection::GetSourcePoints() const { - switch (ConnectionDirection) { - case ManyToOne: return MultiConnections; - case OneToMany: return { &SingleConnection, 1 }; - } -} - -std::span WorkflowConnection::GetDestinationPoints() { - switch (ConnectionDirection) { - case ManyToOne: return { &SingleConnection, 1 }; - case OneToMany: return MultiConnections; - } -} - -std::span WorkflowConnection::GetDestinationPoints() const { - switch (ConnectionDirection) { - case ManyToOne: return { &SingleConnection, 1 }; - case OneToMany: return MultiConnections; - } -} - -bool WorkflowNode::InputPin::IsConstantConnection() const { - return ConnectionToConst && IsConnected(); -} - -bool WorkflowNode::InputPin::IsConnected() const { - return Connection != WorkflowConnection::kInvalidId; -} - -BaseValue::Kind WorkflowNode::InputPin::GetMatchingType() const { - return MatchingType; -} - -WorkflowConnection::Direction WorkflowNode::InputPin::GetSupportedDirection() const { - return AllowsMultipleConnections ? WorkflowConnection::ManyToOne : WorkflowConnection::OneToMany; -} - -bool WorkflowNode::OutputPin::IsConnected() const { - return Connection != WorkflowConnection::kInvalidId; -} - -BaseValue::Kind WorkflowNode::OutputPin::GetMatchingType() const { - return MatchingType; -} - -WorkflowConnection::Direction WorkflowNode::OutputPin::GetSupportedDirection() const { - return AllowsMultipleConnections ? WorkflowConnection::OneToMany : WorkflowConnection::ManyToOne; -} - -WorkflowNode::WorkflowNode(Type type, Kind kind) - : mType{ type } - , mKind{ kind } { -} - -size_t WorkflowNode::GetId() const { - return mId; -} - -WorkflowNode::Type WorkflowNode::GetType() const { - return mType; -} - -WorkflowNode::Kind WorkflowNode::GetKind() const { - return mKind; -} - -void WorkflowNode::ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId) { - mWorkflow->Connect(*this, nodeId, output, outputNodeId); -} - -void WorkflowNode::DisconnectInput(int nodeId) { - mWorkflow->DisconnectByDestination(*this, nodeId); -} - -bool WorkflowNode::IsInputConnected(int nodeId) const { - return mInputs[nodeId].IsConnected(); -} - -void WorkflowNode::ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId) { - mWorkflow->Connect(input, inputNodeId, *this, nodeId); -} - -void WorkflowNode::DisconnectOutput(int nodeId) { - mWorkflow->DisconnectBySource(*this, nodeId); -} - -bool WorkflowNode::IsOutputConnected(int nodeId) const { - return mOutputs[nodeId].IsConnected(); -} - -WorkflowNode::InputPin& WorkflowNode::InsertInputPin(int atIdx) { - assert(atIdx >= 0 && atIdx < mInputs.size()); - - mInputs.push_back(InputPin{}); - for (int i = (int)mInputs.size() - 1, end = atIdx + 1; i >= end; --i) { - SwapInputPin(i, i + 1); - } - - return mInputs[atIdx]; -} - -void WorkflowNode::RemoveInputPin(int pin) { - DisconnectInput(pin); - for (int i = 0, end = (int)mInputs.size() - 1; i < end; ++i) { - SwapInputPin(i, i + 1); - } - mInputs.resize(mInputs.size() - 1); -} - -void WorkflowNode::SwapInputPin(int a, int b) { - auto& pinA = mInputs[a]; - auto& pinB = mInputs[b]; - - if (mWorkflow) { - // Delay assignment to ConnectionPoint::Pin so that the pinB loop (if it happens to run and looks in the same Connection) doesn't match the point updated in the pinA loop - using Pt = WorkflowConnection::ConnectionPoint; - Pt* pointA = nullptr; - Pt* pointB = nullptr; - - if (pinA.IsConnected() && !pinA.IsConstantConnection()) { - auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetDestinationPoints(); - auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a }); - pointA = it == pts.end() ? nullptr : &*it; - } - if (pinB.IsConnected() && !pinB.IsConstantConnection()) { - auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetDestinationPoints(); - auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b }); - pointB = it == pts.end() ? nullptr : &*it; - } - - if (pointA) pointA->Pin = b; - if (pointB) pointB->Pin = a; - } - - std::swap(pinA, pinB); -} - -WorkflowNode::OutputPin& WorkflowNode::InsertOutputPin(int atIdx) { - assert(atIdx >= 0 && atIdx < mOutputs.size()); - - mOutputs.push_back(OutputPin{}); - for (int i = (int)mOutputs.size() - 1, end = atIdx + 1; i >= end; --i) { - SwapOutputPin(i, i + 1); - } - - return mOutputs[atIdx]; -} - -void WorkflowNode::RemoveOutputPin(int pin) { - DisconnectOutput(pin); - for (int i = 0, end = (int)mOutputs.size() - 1; i < end; ++i) { - SwapInputPin(i, i + 1); - } - mOutputs.resize(mOutputs.size() - 1); -} - -void WorkflowNode::SwapOutputPin(int a, int b) { - auto& pinA = mOutputs[a]; - auto& pinB = mOutputs[b]; - - if (mWorkflow) { - using Pt = WorkflowConnection::ConnectionPoint; - Pt* pointA = nullptr; - Pt* pointB = nullptr; - - if (pinA.IsConnected()) { - auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetSourcePoints(); - auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a }); - pointA = it == pts.end() ? nullptr : &*it; - } - if (pinB.IsConnected()) { - auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetSourcePoints(); - auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b }); - pointB = it == pts.end() ? nullptr : &*it; - } - - if (pointA) pointA->Pin = b; - if (pointB) pointB->Pin = a; - } - - std::swap(pinA, pinB); -} - -WorkflowConnection* Workflow::GetConnectionById(size_t id) { - return &mConnections[id]; -} - -WorkflowNode* Workflow::GetStepById(size_t id) { - return mNodes[id].get(); -} - -BaseValue* Workflow::GetConstantById(size_t id) { - return mConstants[id].get(); -} - -void Workflow::AddStep(std::unique_ptr step) { - auto [storage, id] = AllocWorkflowStep(); - storage = std::move(step); - storage->OnAttach(*this, id); - storage->mWorkflow = this; - storage->mId = id; -} - -void Workflow::RemoveStep(size_t id) { - auto& step = mNodes[id]; - if (step == nullptr) return; - - step->OnDetach(); - step->mWorkflow = nullptr; - step->mId = WorkflowNode::kInvalidId; -} - -bool Workflow::Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin) { - auto& src = sourceNode.mOutputs[sourcePin]; - auto& dst = destinationNode.mInputs[destinationPin]; - - // Equivalent to `if (o.GetSupportedDirection() != i.GetSupportedDirection()) return false;` - // Cannot connect two pins of different type - if (src.AllowsMultipleConnections == dst.AllowsMultipleConnections) return false; - // Would be same as `dst.GetSupportedDirection()` because we validated that they are the same above - auto direction = src.GetSupportedDirection(); - - // TODO report error to user? - if (src.GetMatchingType() != dst.GetMatchingType()) { - return false; - } - - using Pt = WorkflowConnection::ConnectionPoint; - Pt* srcConnPt; - Pt* dstConnPt; - size_t connId; - - switch (direction) { - case WorkflowConnection::ManyToOne: { - if (dst.IsConnected()) return false; - - WorkflowConnection* conn; - if (src.IsConnected()) { - conn = &mConnections[src.Connection]; - connId = src.Connection; - } else { - auto p = AllocWorkflowConnection(); - conn = &p.first; - connId = p.second; - } - - srcConnPt = &conn->MultiConnections.emplace_back(); - dstConnPt = &conn->SingleConnection; - } break; - - case WorkflowConnection::OneToMany: { - if (src.IsConnected()) return false; - - WorkflowConnection* conn; - if (dst.IsConnected()) { - conn = &mConnections[src.Connection]; - connId = src.Connection; - } else { - auto p = AllocWorkflowConnection(); - conn = &p.first; - connId = p.second; - } - - srcConnPt = &conn->SingleConnection; - dstConnPt = &conn->MultiConnections.emplace_back(); - } break; - } - - srcConnPt->Node = sourceNode.GetId(); - srcConnPt->Pin = sourcePin; - dstConnPt->Node = destinationNode.GetId(); - dstConnPt->Pin = destinationPin; - - src.Connection = connId; - dst.Connection = connId; - return true; -} - -bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, int sourcePin) { - auto& sp = sourceNode.mOutputs[sourcePin]; - if (!sp.IsConnected()) return false; - - auto& conn = mConnections[sp.Connection]; - - using Pt = WorkflowConnection::ConnectionPoint; - switch (sp.GetSupportedDirection()) { - case WorkflowConnection::ManyToOne: { - // Recessive pin, remove ConnectionPoint associated with this pin only - - auto& vec = conn.MultiConnections; - vec.erase(std::remove(vec.begin(), vec.end(), Pt{ sourceNode.GetId(), sourcePin }), vec.end()); - - sp.Connection = WorkflowConnection::kInvalidId; - } break; - - case WorkflowConnection::OneToMany: { - // Dominate pin, removes whole connection - - for (auto& pt : conn.MultiConnections) { - auto& node = *mNodes[pt.Node]; - node.mInputs[pt.Pin].Connection = WorkflowConnection::kInvalidId; - } - sp.Connection = WorkflowConnection::kInvalidId; - - conn = {}; - } break; - } - - return true; -} - -bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin) { - auto& dp = destinationNode.mInputs[destinationPin]; - if (!dp.IsConnected()) return false; - if (dp.IsConstantConnection()) { - dp.ConnectionToConst = false; - dp.Connection = WorkflowConnection::kInvalidId; - return true; - } - - auto& conn = mConnections[dp.Connection]; - - using Pt = WorkflowConnection::ConnectionPoint; - switch (dp.GetSupportedDirection()) { - case WorkflowConnection::ManyToOne: { - // Dominate pin, removes whole connection - - for (auto& pt : conn.MultiConnections) { - auto& node = *mNodes[pt.Node]; - node.mOutputs[pt.Pin].Connection = WorkflowConnection::kInvalidId; - } - dp.Connection = WorkflowConnection::kInvalidId; - - conn = {}; - } break; - - case WorkflowConnection::OneToMany: { - // Recessive pin, remove ConnectionPoint associated with this pin only - - auto& vec = conn.MultiConnections; - vec.erase(std::remove(vec.begin(), vec.end(), Pt{ destinationNode.GetId(), destinationPin }), vec.end()); - - dp.Connection = WorkflowConnection::kInvalidId; - } break; - } - - return true; -} - -std::pair Workflow::AllocWorkflowConnection() { - for (size_t idx = 0; idx < mConnections.size(); ++idx) { - auto& elm = mConnections[idx]; - if (!elm.IsValid()) { - return { elm, idx }; - } - } - - auto id = mConnections.size(); - return { mConnections.emplace_back(WorkflowConnection{}), id }; -} - -std::pair&, size_t> Workflow::AllocWorkflowStep() { - for (size_t idx = 0; idx < mNodes.size(); ++idx) { - auto& elm = mNodes[idx]; - if (elm == nullptr) { - return { elm, idx }; - } - } - - auto id = mNodes.size(); - return { mNodes.emplace_back(std::unique_ptr()), id }; -} - -namespace { -enum class EvaluationStatus { - Unevaluated, - Success, - Failed, -}; -} // namespace - -struct WorkflowEvaluationContext::RuntimeNode { - EvaluationStatus Status = EvaluationStatus::Unevaluated; -}; - -struct WorkflowEvaluationContext::RuntimeConnection { - std::unique_ptr Value; - - bool IsAvailableValue() const { - return Value != nullptr; - } -}; - -WorkflowEvaluationContext::WorkflowEvaluationContext(Workflow& workflow) - : mWorkflow{ &workflow } { - mRuntimeNodes.resize(workflow.mNodes.size()); - mRuntimeConnections.resize(workflow.mConnections.size()); -} - -BaseValue* WorkflowEvaluationContext::GetConnectionValue(size_t id, bool constant) { - if (constant) { - return mWorkflow->GetConstantById(id); - } else { - return mRuntimeConnections[id].Value.get(); - } -} - -BaseValue* WorkflowEvaluationContext::GetConnectionValue(const WorkflowNode::InputPin& inputPin) { - if (inputPin.IsConnected()) { - return GetConnectionValue(inputPin.Connection, inputPin.IsConstantConnection()); - } else { - return nullptr; - } -} - -void WorkflowEvaluationContext::SetConnectionValue(size_t id, std::unique_ptr value) { - mRuntimeConnections[id].Value = std::move(value); -} - -void WorkflowEvaluationContext::SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr value) { - if (outputPin.IsConnected()) { - SetConnectionValue(outputPin.Connection, std::move(value)); - } -} - -void WorkflowEvaluationContext::Run() { - std::queue candidates; // Stores index to nodes - int evaluatedCount = 0; - int erroredCount = 0; - - // Evaluate all the input nodes first - for (size_t i = 0; i < mRuntimeNodes.size(); ++i) { - if (mWorkflow->mNodes[i]->GetType() == WorkflowNode::InputType) { - candidates.push(i); - } - } - - auto AddOutputsToCandidates = [&](size_t idx) { - auto& node = *mWorkflow->mNodes[idx]; - auto& rNode = mRuntimeNodes[idx]; - for (auto& pin : node.mOutputs) { - if (!pin.IsConnected()) continue; - // TODO support the other variant - if (pin.GetSupportedDirection() != WorkflowConnection::OneToMany) continue; - - auto& rConn = mRuntimeConnections[pin.Connection]; - auto& conn = mWorkflow->mConnections[pin.Connection]; - if (rConn.IsAvailableValue()) { - for (WorkflowConnection::ConnectionPoint& cp : conn.MultiConnections) { - if (rNode.Status != EvaluationStatus::Unevaluated) { - candidates.push(cp.Node); - } - } - } - } - }; - auto FindCandidates = [&]() { - for (size_t i = 0; i < mWorkflow->mNodes.size(); ++i) { - auto& node = mWorkflow->mNodes[i]; - auto& rNode = mRuntimeNodes[i]; - - if (rNode.Status != EvaluationStatus::Unevaluated) { - continue; - } - - for (auto& pin : node->mInputs) { - if (!pin.IsConnected()) continue; - - auto& rConn = mRuntimeConnections[pin.Connection]; - if (!rConn.IsAvailableValue()) { - goto skip; - } - } - - candidates.push(i); - - skip: - continue; - } - }; - - while (true) { - while (!candidates.empty()) { - auto idx = candidates.front(); - auto& node = *mWorkflow->mNodes[idx]; - auto& rNode = mRuntimeNodes[idx]; - candidates.pop(); - - int preEvalErrors = mErrors.size(); - node.Evaluate(*this); - if (preEvalErrors != mErrors.size()) { - erroredCount++; - } else { - evaluatedCount++; - AddOutputsToCandidates(idx); - } - } - - if (evaluatedCount + erroredCount >= mRuntimeNodes.size()) { - break; - } - - // Candidates empty, but there are still possibly-evaluable nodes - FindCandidates(); - } - - for (size_t i = 0; i < mRuntimeNodes.size(); ++i) { - if (mWorkflow->mNodes[i]->GetType() == WorkflowNode::OutputType) { - // TODO - } - } -} - -void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node, int pinId, bool inputPin) { - mErrors.push_back(WorkflowEvaluationError{ - .Message = std::move(message), - .NodeId = node.GetId(), - .PinId = pinId, - .PinType = inputPin ? WorkflowEvaluationError::InputPin : WorkflowEvaluationError::OutputPin, - .Type = WorkflowEvaluationError::Error, - }); -} - -void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node) { - mErrors.push_back(WorkflowEvaluationError{ - .Message = std::move(message), - .NodeId = node.GetId(), - .PinId = -1, - .PinType = WorkflowEvaluationError::NoPin, - .Type = WorkflowEvaluationError::Error, - }); -} - -void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node, int pinId, bool inputPin) { - mErrors.push_back(WorkflowEvaluationError{ - .Message = std::move(message), - .NodeId = node.GetId(), - .PinId = pinId, - .PinType = inputPin ? WorkflowEvaluationError::InputPin : WorkflowEvaluationError::OutputPin, - .Type = WorkflowEvaluationError::Warning, - }); -} - -void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node) { - mErrors.push_back(WorkflowEvaluationError{ - .Message = std::move(message), - .NodeId = node.GetId(), - .PinId = -1, - .PinType = WorkflowEvaluationError::NoPin, - .Type = WorkflowEvaluationError::Warning, - }); -} diff --git a/core/src/Model/Workflow.hpp b/core/src/Model/Workflow.hpp deleted file mode 100644 index f1459ba..0000000 --- a/core/src/Model/Workflow.hpp +++ /dev/null @@ -1,211 +0,0 @@ -#pragma once - -#include "Model/EvaluatedValue.hpp" -#include "cplt_fwd.hpp" - -#include -#include -#include -#include -#include -#include - -class WorkflowConnection { -public: - static constexpr auto kInvalidId = std::numeric_limits::max(); - - enum Direction { - ManyToOne, - OneToMany, - }; - - struct ConnectionPoint { - size_t Node; - int Pin; - - bool operator==(const ConnectionPoint&) const = default; - }; - - std::vector MultiConnections; - ConnectionPoint SingleConnection; - Direction ConnectionDirection; - -public: - WorkflowConnection(); - - bool IsValid() const; - std::span GetSourcePoints(); - std::span GetSourcePoints() const; - std::span GetDestinationPoints(); - std::span GetDestinationPoints() const; -}; - -class WorkflowNode { -public: - static constexpr auto kInvalidId = std::numeric_limits::max(); - - enum Type { - InputType, - TransformType, - OutputType, - }; - - enum Kind { - KD_NumericAddition, - KD_NumericSubtraction, - KD_NumericMultiplication, - KD_NumericDivision, - KD_NumericExpression, - KD_TextFormatting, - KD_DocumentTemplateExpansion, - KD_FormInput, - KD_DatabaseRowsInput, - - InvalidKind, - KindCount = InvalidKind, - }; - -protected: - struct InputPin { - size_t Connection = WorkflowConnection::kInvalidId; - BaseValue::Kind MatchingType = BaseValue::KindInvalid; - bool ConnectionToConst = false; - bool AllowsMultipleConnections = false; - - /// A constant connection connects from a user-specified constant value, feeding to a valid \c Destination and \c DestinationPin (i.e. input pins). - bool IsConstantConnection() const; - bool IsConnected() const; - BaseValue::Kind GetMatchingType() const; - WorkflowConnection::Direction GetSupportedDirection() const; - }; - - struct OutputPin { - size_t Connection = WorkflowConnection::kInvalidId; - BaseValue::Kind MatchingType = BaseValue::KindInvalid; - bool AllowsMultipleConnections = false; - - bool IsConnected() const; - BaseValue::Kind GetMatchingType() const; - WorkflowConnection::Direction GetSupportedDirection() const; - }; - - friend class Workflow; - friend class WorkflowEvaluationContext; - - Workflow* mWorkflow; - size_t mId; - std::vector mInputs; - std::vector mOutputs; - Type mType; - Kind mKind; - -public: - WorkflowNode(Type type, Kind kind); - virtual ~WorkflowNode() = default; - - WorkflowNode(const WorkflowNode&) = delete; - WorkflowNode& operator=(const WorkflowNode&) = delete; - WorkflowNode(WorkflowNode&&) = default; - WorkflowNode& operator=(WorkflowNode&&) = default; - - size_t GetId() const; - Type GetType() const; - Kind GetKind() const; - - void ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId); - void DisconnectInput(int nodeId); - bool IsInputConnected(int nodeId) const; - - void ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId); - void DisconnectOutput(int nodeId); - bool IsOutputConnected(int nodeId) const; - - virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0; - -protected: - InputPin& InsertInputPin(int atIdx); - void RemoveInputPin(int pin); - void SwapInputPin(int a, int b); - OutputPin& InsertOutputPin(int atIdx); - void RemoveOutputPin(int pin); - void SwapOutputPin(int a, int b); - - /* For \c Workflow to invoke, override by implementations */ - - void OnAttach(Workflow& workflow, size_t newId) {} - void OnDetach() {} -}; - -class Workflow { -private: - friend class WorkflowEvaluationContext; - - std::vector mConnections; - std::vector> mNodes; - std::vector> mConstants; - -public: - WorkflowConnection* GetConnectionById(size_t id); - WorkflowNode* GetStepById(size_t id); - BaseValue* GetConstantById(size_t id); - - void AddStep(std::unique_ptr step); - void RemoveStep(size_t id); - - bool Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin); - bool DisconnectBySource(WorkflowNode& sourceNode, int sourcePin); - bool DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin); - -private: - std::pair AllocWorkflowConnection(); - std::pair&, size_t> AllocWorkflowStep(); -}; - -class WorkflowEvaluationError { -public: - enum MessageType : int16_t { - Error, - Warning, - }; - - enum PinType : int16_t { - NoPin, - InputPin, - OutputPin, - }; - -public: - std::string Message; - size_t NodeId; - int PinId; - PinType PinType; - MessageType Type; -}; - -class WorkflowEvaluationContext { -private: - struct RuntimeNode; - struct RuntimeConnection; - - Workflow* mWorkflow; - std::vector mRuntimeNodes; - std::vector mRuntimeConnections; - std::vector mErrors; - std::vector mWarnings; - -public: - WorkflowEvaluationContext(Workflow& workflow); - - BaseValue* GetConnectionValue(size_t id, bool constant); - BaseValue* GetConnectionValue(const WorkflowNode::InputPin& inputPin); - void SetConnectionValue(size_t id, std::unique_ptr value); - void SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr value); - - void ReportError(std::string message, const WorkflowNode& node, int pinId, bool inputPin); - void ReportError(std::string message, const WorkflowNode& node); - void ReportWarning(std::string message, const WorkflowNode& node, int pinId, bool inputPin); - void ReportWarning(std::string message, const WorkflowNode& node); - - /// Run until all possible paths have been evaluated. - void Run(); -}; diff --git a/core/src/Model/Workflow/Evaluation.cpp b/core/src/Model/Workflow/Evaluation.cpp new file mode 100644 index 0000000..111d34e --- /dev/null +++ b/core/src/Model/Workflow/Evaluation.cpp @@ -0,0 +1,183 @@ +#include "Evaluation.hpp" + +#include + +namespace { +enum class EvaluationStatus { + Unevaluated, + Success, + Failed, +}; +} // namespace + +struct WorkflowEvaluationContext::RuntimeNode { + EvaluationStatus Status = EvaluationStatus::Unevaluated; +}; + +struct WorkflowEvaluationContext::RuntimeConnection { + std::unique_ptr Value; + + bool IsAvailableValue() const { + return Value != nullptr; + } +}; + +WorkflowEvaluationContext::WorkflowEvaluationContext(Workflow& workflow) + : mWorkflow{ &workflow } { + mRuntimeNodes.resize(workflow.mNodes.size()); + mRuntimeConnections.resize(workflow.mConnections.size()); +} + +BaseValue* WorkflowEvaluationContext::GetConnectionValue(size_t id, bool constant) { + if (constant) { + return mWorkflow->GetConstantById(id); + } else { + return mRuntimeConnections[id].Value.get(); + } +} + +BaseValue* WorkflowEvaluationContext::GetConnectionValue(const WorkflowNode::InputPin& inputPin) { + if (inputPin.IsConnected()) { + return GetConnectionValue(inputPin.Connection, inputPin.IsConstantConnection()); + } else { + return nullptr; + } +} + +void WorkflowEvaluationContext::SetConnectionValue(size_t id, std::unique_ptr value) { + mRuntimeConnections[id].Value = std::move(value); +} + +void WorkflowEvaluationContext::SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr value) { + if (outputPin.IsConnected()) { + SetConnectionValue(outputPin.Connection, std::move(value)); + } +} + +void WorkflowEvaluationContext::Run() { + std::queue candidates; // Stores index to nodes + int evaluatedCount = 0; + int erroredCount = 0; + + // Evaluate all the input nodes first + for (size_t i = 0; i < mRuntimeNodes.size(); ++i) { + if (mWorkflow->mNodes[i]->GetType() == WorkflowNode::InputType) { + candidates.push(i); + } + } + + auto AddOutputsToCandidates = [&](size_t idx) { + auto& node = *mWorkflow->mNodes[idx]; + auto& rNode = mRuntimeNodes[idx]; + for (auto& pin : node.mOutputs) { + if (!pin.IsConnected()) continue; + // TODO support the other variant + if (pin.GetSupportedDirection() != WorkflowConnection::OneToMany) continue; + + auto& rConn = mRuntimeConnections[pin.Connection]; + auto& conn = mWorkflow->mConnections[pin.Connection]; + if (rConn.IsAvailableValue()) { + for (WorkflowConnection::ConnectionPoint& cp : conn.MultiConnections) { + if (rNode.Status != EvaluationStatus::Unevaluated) { + candidates.push(cp.Node); + } + } + } + } + }; + auto FindCandidates = [&]() { + for (size_t i = 0; i < mWorkflow->mNodes.size(); ++i) { + auto& node = mWorkflow->mNodes[i]; + auto& rNode = mRuntimeNodes[i]; + + if (rNode.Status != EvaluationStatus::Unevaluated) { + continue; + } + + for (auto& pin : node->mInputs) { + if (!pin.IsConnected()) continue; + + auto& rConn = mRuntimeConnections[pin.Connection]; + if (!rConn.IsAvailableValue()) { + goto skip; + } + } + + candidates.push(i); + + skip: + continue; + } + }; + + while (true) { + while (!candidates.empty()) { + auto idx = candidates.front(); + auto& node = *mWorkflow->mNodes[idx]; + auto& rNode = mRuntimeNodes[idx]; + candidates.pop(); + + int preEvalErrors = mErrors.size(); + node.Evaluate(*this); + if (preEvalErrors != mErrors.size()) { + erroredCount++; + } else { + evaluatedCount++; + AddOutputsToCandidates(idx); + } + } + + if (evaluatedCount + erroredCount >= mRuntimeNodes.size()) { + break; + } + + // Candidates empty, but there are still possibly-evaluable nodes + FindCandidates(); + } + + for (size_t i = 0; i < mRuntimeNodes.size(); ++i) { + if (mWorkflow->mNodes[i]->GetType() == WorkflowNode::OutputType) { + // TODO + } + } +} + +void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node, int pinId, bool inputPin) { + mErrors.push_back(WorkflowEvaluationError{ + .Message = std::move(message), + .NodeId = node.GetId(), + .PinId = pinId, + .PinType = inputPin ? WorkflowEvaluationError::InputPin : WorkflowEvaluationError::OutputPin, + .Type = WorkflowEvaluationError::Error, + }); +} + +void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node) { + mErrors.push_back(WorkflowEvaluationError{ + .Message = std::move(message), + .NodeId = node.GetId(), + .PinId = -1, + .PinType = WorkflowEvaluationError::NoPin, + .Type = WorkflowEvaluationError::Error, + }); +} + +void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node, int pinId, bool inputPin) { + mErrors.push_back(WorkflowEvaluationError{ + .Message = std::move(message), + .NodeId = node.GetId(), + .PinId = pinId, + .PinType = inputPin ? WorkflowEvaluationError::InputPin : WorkflowEvaluationError::OutputPin, + .Type = WorkflowEvaluationError::Warning, + }); +} + +void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node) { + mErrors.push_back(WorkflowEvaluationError{ + .Message = std::move(message), + .NodeId = node.GetId(), + .PinId = -1, + .PinType = WorkflowEvaluationError::NoPin, + .Type = WorkflowEvaluationError::Warning, + }); +} diff --git a/core/src/Model/Workflow/Evaluation.hpp b/core/src/Model/Workflow/Evaluation.hpp new file mode 100644 index 0000000..be2e862 --- /dev/null +++ b/core/src/Model/Workflow/Evaluation.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "Model/Workflow/Workflow.hpp" + +#include +#include +#include +#include + +class WorkflowEvaluationError { +public: + enum MessageType : int16_t { + Error, + Warning, + }; + + enum PinType : int16_t { + NoPin, + InputPin, + OutputPin, + }; + +public: + std::string Message; + size_t NodeId; + int PinId; + PinType PinType; + MessageType Type; +}; + +class WorkflowEvaluationContext { +private: + struct RuntimeNode; + struct RuntimeConnection; + + Workflow* mWorkflow; + std::vector mRuntimeNodes; + std::vector mRuntimeConnections; + std::vector mErrors; + std::vector mWarnings; + +public: + WorkflowEvaluationContext(Workflow& workflow); + + BaseValue* GetConnectionValue(size_t id, bool constant); + BaseValue* GetConnectionValue(const WorkflowNode::InputPin& inputPin); + void SetConnectionValue(size_t id, std::unique_ptr value); + void SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr value); + + void ReportError(std::string message, const WorkflowNode& node, int pinId, bool inputPin); + void ReportError(std::string message, const WorkflowNode& node); + void ReportWarning(std::string message, const WorkflowNode& node, int pinId, bool inputPin); + void ReportWarning(std::string message, const WorkflowNode& node); + + /// Run until all possible paths have been evaluated. + void Run(); +}; diff --git a/core/src/Model/Workflow/Nodes/DocumentNodes.cpp b/core/src/Model/Workflow/Nodes/DocumentNodes.cpp new file mode 100644 index 0000000..66d2eae --- /dev/null +++ b/core/src/Model/Workflow/Nodes/DocumentNodes.cpp @@ -0,0 +1,15 @@ +#include "DocumentNodes.hpp" + +#include "Model/Workflow/Evaluation.hpp" +#include "Model/Workflow/Values/BasicValues.hpp" + +bool DocumentTemplateExpansionNode::IsInstance(const WorkflowNode* node) { + return node->GetKind() == KD_DocumentTemplateExpansion; +} + +DocumentTemplateExpansionNode::DocumentTemplateExpansionNode() + : WorkflowNode(TransformType, KD_DocumentTemplateExpansion) { +} + +void DocumentTemplateExpansionNode::Evaluate(WorkflowEvaluationContext& ctx) { +} diff --git a/core/src/Model/Workflow/Nodes/DocumentNodes.hpp b/core/src/Model/Workflow/Nodes/DocumentNodes.hpp new file mode 100644 index 0000000..3b775ec --- /dev/null +++ b/core/src/Model/Workflow/Nodes/DocumentNodes.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "Model/Workflow/Workflow.hpp" + +class DocumentTemplateExpansionNode : public WorkflowNode { +public: + static bool IsInstance(const WorkflowNode* node); + DocumentTemplateExpansionNode(); + + // TODO + virtual void Evaluate(WorkflowEvaluationContext& ctx) override; +}; diff --git a/core/src/Model/Workflow/Nodes/NumericNodes.cpp b/core/src/Model/Workflow/Nodes/NumericNodes.cpp new file mode 100644 index 0000000..1722224 --- /dev/null +++ b/core/src/Model/Workflow/Nodes/NumericNodes.cpp @@ -0,0 +1,86 @@ +#include "NumericNodes.hpp" + +#include "Model/Workflow/Evaluation.hpp" +#include "Model/Workflow/Values/BasicValues.hpp" +#include "Utils/Macros.hpp" +#include "Utils/RTTI.hpp" + +#include +#include + +WorkflowNode::Kind NumericOperationNode::OperationTypeToNodeKind(OperationType type) { + switch (type) { + case Addition: return KD_NumericAddition; + case Subtraction: return KD_NumericSubtraction; + case Multiplication: return KD_NumericMultiplication; + case Division: return KD_NumericDivision; + default: UNREACHABLE; + } +} + +NumericOperationNode::OperationType NumericOperationNode::NodeKindToOperationType(Kind kind) { + switch (kind) { + case KD_NumericAddition: return Addition; + case KD_NumericSubtraction: return Subtraction; + case KD_NumericMultiplication: return Multiplication; + case KD_NumericDivision: return Division; + default: UNREACHABLE; + } +} + +bool NumericOperationNode::IsInstance(const WorkflowNode* node) { + return node->GetKind() >= KD_NumericAddition && node->GetKind() <= KD_NumericDivision; +} + +NumericOperationNode::NumericOperationNode(OperationType type) + : WorkflowNode(TransformType, OperationTypeToNodeKind(type)) + , mType{ type } { + mInputs.resize(2); + mInputs[0].MatchingType = BaseValue::KD_Numeric; + mInputs[1].MatchingType = BaseValue::KD_Numeric; + + mOutputs.resize(1); + mOutputs[0].MatchingType = BaseValue::KD_Numeric; +} + +void NumericOperationNode::Evaluate(WorkflowEvaluationContext& ctx) { + auto lhsVal = dyn_cast(ctx.GetConnectionValue(mInputs[0])); + if (!lhsVal) return; + double lhs = lhsVal->GetValue(); + + auto rhsVal = dyn_cast(ctx.GetConnectionValue(mInputs[1])); + if (!rhsVal) return; + double rhs = rhsVal->GetValue(); + + double res; + switch (mType) { + case Addition: res = lhs + rhs; break; + case Subtraction: res = lhs - rhs; break; + case Multiplication: res = lhs * rhs; break; + case Division: { + if (rhs == 0.0) { + // TODO localize + ctx.ReportError("Error: division by 0", *this); + return; + } + res = lhs / rhs; + } break; + + default: return; + } + + auto value = std::make_unique(); + value->SetValue(res); + ctx.SetConnectionValue(mOutputs[0], std::move(value)); +} + +bool NumericExpressionNode::IsInstance(const WorkflowNode* node) { + return node->GetKind() == KD_NumericExpression; +} + +NumericExpressionNode::NumericExpressionNode() + : WorkflowNode(TransformType, KD_NumericExpression) { +} + +void NumericExpressionNode::Evaluate(WorkflowEvaluationContext& ctx) { +} diff --git a/core/src/Model/Workflow/Nodes/NumericNodes.hpp b/core/src/Model/Workflow/Nodes/NumericNodes.hpp new file mode 100644 index 0000000..32610f6 --- /dev/null +++ b/core/src/Model/Workflow/Nodes/NumericNodes.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "Model/Workflow/Workflow.hpp" + +#include +#include +#include +#include + +class NumericOperationNode : public WorkflowNode { +public: + enum OperationType { + Addition, + Subtraction, + Multiplication, + Division, + + InvalidType, + TypeCount = InvalidType, + }; + +private: + OperationType mType; + +public: + static Kind OperationTypeToNodeKind(OperationType type); + static OperationType NodeKindToOperationType(Kind kind); + static bool IsInstance(const WorkflowNode* node); + NumericOperationNode(OperationType type); + + virtual void Evaluate(WorkflowEvaluationContext& ctx) override; +}; + +class NumericExpressionNode : public WorkflowNode { +public: + static bool IsInstance(const WorkflowNode* node); + NumericExpressionNode(); + + // TODO + virtual void Evaluate(WorkflowEvaluationContext& ctx) override; +}; \ No newline at end of file diff --git a/core/src/Model/Workflow/Nodes/TextNodes.cpp b/core/src/Model/Workflow/Nodes/TextNodes.cpp new file mode 100644 index 0000000..3852c66 --- /dev/null +++ b/core/src/Model/Workflow/Nodes/TextNodes.cpp @@ -0,0 +1,215 @@ +#include "TextNodes.hpp" + +#include "Model/Workflow/Evaluation.hpp" +#include "Model/Workflow/Values/BasicValues.hpp" +#include "Utils/Macros.hpp" +#include "Utils/RTTI.hpp" +#include "Utils/Variant.hpp" + +#include +#include +#include +#include + +class TextFormatterNode::Impl { +public: + template + static void ForArguments(std::vector::iterator begin, std::vector::iterator end, const TFunction& func) { + for (auto it = begin; it != end; ++it) { + auto& elm = *it; + if (auto arg = std::get_if(&elm)) { + func(*arg); + } + } + } + + /// Find the pin index that the \c elmIdx -th element should have, based on the elements coming before it. + static int FindPinForElement(const std::vector& vec, int elmIdx) { + for (int i = elmIdx; i >= 0; --i) { + auto& elm = vec[i]; + if (auto arg = std::get_if(&elm)) { + return arg->PinIdx + 1; + } + } + return 0; + } +}; + +BaseValue::Kind TextFormatterNode::ArgumentTypeToValueKind(TextFormatterNode::ArgumentType arg) { + switch (arg) { + case NumericArgument: return BaseValue::KD_Numeric; + case TextArgument: return BaseValue::KD_Text; + case DateTimeArgument: return BaseValue::KD_DateTime; + default: UNREACHABLE; + } +} + +bool TextFormatterNode::IsInstance(const WorkflowNode* node) { + return node->GetKind() == KD_TextFormatting; +} + +TextFormatterNode::TextFormatterNode() + : WorkflowNode(TransformType, KD_TextFormatting) { +} + +int TextFormatterNode::GetElementCount() const { + return mElements.size(); +} + +const TextFormatterNode::Element& TextFormatterNode::GetElement(int idx) const { + return mElements[idx]; +} + +void TextFormatterNode::SetElement(int idx, std::string text) { + assert(idx >= 0 && idx < mElements.size()); + + std::visit( + Overloaded{ + [&](const std::string& original) { mMinOutputChars -= original.size(); }, + [&](const Argument& original) { PreRemoveElement(idx); }, + }, + mElements[idx]); + + mMinOutputChars += text.size(); + mElements[idx] = std::move(text); +} + +void TextFormatterNode::SetElement(int idx, ArgumentType argument) { + assert(idx >= 0 && idx < mElements.size()); + + std::visit( + Overloaded{ + [&](const std::string& original) { + mMinOutputChars -= original.size(); + + mElements[idx] = Argument{ + .ArgumentType = argument, + .PinIdx = Impl::FindPinForElement(mElements, idx), + }; + /* `original` is invalid from this point */ + }, + [&](const Argument& original) { + int pinIdx = original.PinIdx; + + // Create pin + auto& pin = mInputs[pinIdx]; + pin.MatchingType = ArgumentTypeToValueKind(argument); + + // Create element + mElements[idx] = Argument{ + .ArgumentType = argument, + .PinIdx = pinIdx, + }; + /* `original` is invalid from this point */ + }, + }, + mElements[idx]); +} + +void TextFormatterNode::InsertElement(int idx, std::string text) { + assert(idx >= 0); + if (idx >= mElements.size()) AppendElement(std::move(text)); + + mMinOutputChars += text.size(); + mElements.insert(mElements.begin() + idx, std::move(text)); +} + +void TextFormatterNode::InsertElement(int idx, ArgumentType argument) { + assert(idx >= 0); + if (idx >= mElements.size()) AppendElement(argument); + + int pinIdx = Impl::FindPinForElement(mElements, idx); + + // Create pin + auto& pin = InsertInputPin(pinIdx); + pin.MatchingType = ArgumentTypeToValueKind(argument); + + // Create element + mElements.insert( + mElements.begin() + idx, + Argument{ + .ArgumentType = argument, + .PinIdx = pinIdx, + }); +} + +void TextFormatterNode::AppendElement(std::string text) { + mMinOutputChars += text.size(); + mElements.push_back(std::move(text)); +} + +void TextFormatterNode::AppendElement(ArgumentType argument) { + int pinIdx = mInputs.size(); + // Create pin + mInputs.push_back(InputPin{}); + mInputs.back().MatchingType = ArgumentTypeToValueKind(argument); + // Creat eelement + mElements.push_back(Argument{ + .ArgumentType = argument, + .PinIdx = pinIdx, + }); +} + +void TextFormatterNode::RemoveElement(int idx) { + assert(idx >= 0 && idx < mElements.size()); + + PreRemoveElement(idx); + if (auto arg = std::get_if(&mElements[idx])) { + RemoveInputPin(arg->PinIdx); + } + mElements.erase(mElements.begin() + idx); +} + +void TextFormatterNode::Evaluate(WorkflowEvaluationContext& ctx) { + std::string result; + result.reserve((size_t)(mMinOutputChars * 1.5f)); + + auto HandleText = [&](const std::string& str) { + result += str; + }; + auto HandleArgument = [&](const Argument& arg) { + switch (arg.ArgumentType) { + case NumericArgument: { + if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { + result += val->GetString(); + } else { + // TODO localize + ctx.ReportError("Non-numeric value connected to a numeric text format parameter.", *this); + } + } break; + case TextArgument: { + if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { + result += val->GetValue(); + } else { + // TODO localize + ctx.ReportError("Non-text value connected to a textual text format parameter.", *this); + } + } break; + case DateTimeArgument: { + if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { + result += val->GetString(); + } else { + // TODO localize + ctx.ReportError("Non-date/time value connected to a date/time text format parameter.", *this); + } + } break; + } + }; + + for (auto& elm : mElements) { + std::visit(Overloaded{ HandleText, HandleArgument }, elm); + } +} + +void TextFormatterNode::PreRemoveElement(int idx) { + auto& elm = mElements[idx]; + if (auto arg = std::get_if(&elm)) { + RemoveInputPin(arg->PinIdx); + Impl::ForArguments( + mElements.begin() + idx + 1, + mElements.end(), + [&](Argument& arg) { + arg.PinIdx--; + }); + } +} \ No newline at end of file diff --git a/core/src/Model/Workflow/Nodes/TextNodes.hpp b/core/src/Model/Workflow/Nodes/TextNodes.hpp new file mode 100644 index 0000000..278db32 --- /dev/null +++ b/core/src/Model/Workflow/Nodes/TextNodes.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "Model/Workflow/Workflow.hpp" + +#include +#include +#include +#include + +class TextFormatterNode : public WorkflowNode { +public: + enum ArgumentType { + NumericArgument, + TextArgument, + DateTimeArgument, + }; + +private: + class Impl; + + struct Argument { + ArgumentType ArgumentType; + int PinIdx; + }; + using Element = std::variant; + + std::vector mElements; + int mMinOutputChars; + +public: + static BaseValue::Kind ArgumentTypeToValueKind(ArgumentType arg); + static bool IsInstance(const WorkflowNode* node); + TextFormatterNode(); + + int GetElementCount() const; + const Element& GetElement(int idx) const; + + void SetElement(int idx, std::string text); + void SetElement(int idx, ArgumentType argument); + void InsertElement(int idx, std::string text); + void InsertElement(int idx, ArgumentType argument); + void AppendElement(std::string text); + void AppendElement(ArgumentType argument); + void RemoveElement(int idx); + + virtual void Evaluate(WorkflowEvaluationContext& ctx) override; + +private: + void PreRemoveElement(int idx); +}; \ No newline at end of file diff --git a/core/src/Model/Workflow/Nodes/UserInputNodes.cpp b/core/src/Model/Workflow/Nodes/UserInputNodes.cpp new file mode 100644 index 0000000..f59226a --- /dev/null +++ b/core/src/Model/Workflow/Nodes/UserInputNodes.cpp @@ -0,0 +1,26 @@ +#include "UserInputNodes.hpp" + +#include "Model/Workflow/Evaluation.hpp" +#include "Model/Workflow/Values/BasicValues.hpp" + +bool FormInputNode::IsInstance(const WorkflowNode* node) { + return node->GetKind() == KD_FormInput; +} + +FormInputNode::FormInputNode() + : WorkflowNode(InputType, KD_FormInput) { +} + +void FormInputNode::Evaluate(WorkflowEvaluationContext& ctx) { +} + +bool DatabaseRowsInputNode::IsInstance(const WorkflowNode* node) { + return node->GetKind() == KD_DatabaseRowsInput; +} + +DatabaseRowsInputNode::DatabaseRowsInputNode() + : WorkflowNode(InputType, KD_DatabaseRowsInput) { +} + +void DatabaseRowsInputNode::Evaluate(WorkflowEvaluationContext& ctx) { +} diff --git a/core/src/Model/Workflow/Nodes/UserInputNodes.hpp b/core/src/Model/Workflow/Nodes/UserInputNodes.hpp new file mode 100644 index 0000000..fe66cb4 --- /dev/null +++ b/core/src/Model/Workflow/Nodes/UserInputNodes.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "Model/Workflow/Workflow.hpp" + +class FormInputNode : public WorkflowNode { +public: + static bool IsInstance(const WorkflowNode* node); + FormInputNode(); + + // TODO + virtual void Evaluate(WorkflowEvaluationContext& ctx) override; +}; + +class DatabaseRowsInputNode : public WorkflowNode { +public: + static bool IsInstance(const WorkflowNode* node); + DatabaseRowsInputNode(); + + // TODO + virtual void Evaluate(WorkflowEvaluationContext& ctx) override; +}; diff --git a/core/src/Model/Workflow/Nodes/fwd.hpp b/core/src/Model/Workflow/Nodes/fwd.hpp new file mode 100644 index 0000000..4153825 --- /dev/null +++ b/core/src/Model/Workflow/Nodes/fwd.hpp @@ -0,0 +1,15 @@ +#pragma once + +// DocumentNodes.hpp +class DocumentTemplateExpansionNode; + +// InputNodes.hpp +class FormInputNode; +class DatabaseRowsInputNode; + +// NumericNodes.hpp +class NumericOperationNode; +class NumericExpressionNode; + +// TextNodes.hpp +class TextFormatterNode; diff --git a/core/src/Model/Workflow/Value.cpp b/core/src/Model/Workflow/Value.cpp new file mode 100644 index 0000000..7e5aabf --- /dev/null +++ b/core/src/Model/Workflow/Value.cpp @@ -0,0 +1,9 @@ +#include "Value.hpp" + +BaseValue::BaseValue(Kind kind) + : mKind{ kind } { +} + +BaseValue::Kind BaseValue::GetKind() const { + return mKind; +} diff --git a/core/src/Model/Workflow/Value.hpp b/core/src/Model/Workflow/Value.hpp new file mode 100644 index 0000000..eb99c14 --- /dev/null +++ b/core/src/Model/Workflow/Value.hpp @@ -0,0 +1,28 @@ +#pragma once + +class BaseValue { +public: + enum Kind { + KD_Numeric, + KD_Text, + KD_DateTime, + + /// An unspecified type, otherwise known as "any" in some contexts. + KindInvalid, + KindCount = KindInvalid, + }; + +private: + Kind mKind; + +public: + BaseValue(Kind kind); + virtual ~BaseValue() = default; + + BaseValue(const BaseValue&) = delete; + BaseValue& operator=(const BaseValue&) = delete; + BaseValue(BaseValue&&) = default; + BaseValue& operator=(BaseValue&&) = default; + + Kind GetKind() const; +}; diff --git a/core/src/Model/Workflow/Values/BasicValues.cpp b/core/src/Model/Workflow/Values/BasicValues.cpp new file mode 100644 index 0000000..fd70acd --- /dev/null +++ b/core/src/Model/Workflow/Values/BasicValues.cpp @@ -0,0 +1,76 @@ +#include "BasicValues.hpp" + +#include + +bool NumericValue::IsInstance(const BaseValue* value) { + return value->GetKind() == KD_Numeric; +} + +NumericValue::NumericValue() + : BaseValue(BaseValue::KD_Numeric) { +} + +std::string NumericValue::GetString() const { + char buf[64]; + auto res = std::to_chars(buf, buf + std::size(buf), mValue); + if (res.ec == std::errc()) { + return std::string(buf, res.ptr); + } else { + // TODO larger buffer + return ""; + } +} + +int64_t NumericValue::GetInt() const { + return static_cast(mValue); +} + +double NumericValue::GetValue() const { + return mValue; +} + +void NumericValue::SetValue(double value) { + mValue = value; +} + +bool TextValue::IsInstance(const BaseValue* value) { + return value->GetKind() == KD_Text; +} + +TextValue::TextValue() + : BaseValue(BaseValue::KD_Text) { +} + +const std::string& TextValue::GetValue() const { + return mValue; +} + +void TextValue::SetValue(const std::string& value) { + mValue = value; +} + +bool DateTimeValue::IsInstance(const BaseValue* value) { + return value->GetKind() == KD_DateTime; +} + +DateTimeValue::DateTimeValue() + : BaseValue(BaseValue::KD_DateTime) { +} + +std::string DateTimeValue::GetString() const { + namespace chrono = std::chrono; + auto t = chrono::system_clock::to_time_t(mValue); + + char data[32]; + std::strftime(data, sizeof(data), "%Y-%m-%d %H:%M:%S", std::localtime(&t)); + + return std::string(data); +} + +const std::chrono::time_point& DateTimeValue::GetValue() const { + return mValue; +} + +void DateTimeValue::SetValue(const std::chrono::time_point& value) { + mValue = value; +} diff --git a/core/src/Model/Workflow/Values/BasicValues.hpp b/core/src/Model/Workflow/Values/BasicValues.hpp new file mode 100644 index 0000000..a116c8c --- /dev/null +++ b/core/src/Model/Workflow/Values/BasicValues.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "Model/Workflow/Value.hpp" + +#include +#include +#include + +class NumericValue : public BaseValue { +private: + double mValue; + +public: + static bool IsInstance(const BaseValue* value); + NumericValue(); + + std::string GetString() const; + int64_t GetInt() const; + double GetValue() const; + void SetValue(double value); +}; + +class TextValue : public BaseValue { +private: + std::string mValue; + +public: + static bool IsInstance(const BaseValue* value); + TextValue(); + + const std::string& GetValue() const; + void SetValue(const std::string& value); +}; + +class DateTimeValue : public BaseValue { +private: + std::chrono::time_point mValue; + +public: + static bool IsInstance(const BaseValue* value); + DateTimeValue(); + + std::string GetString() const; + const std::chrono::time_point& GetValue() const; + void SetValue(const std::chrono::time_point& value); +}; diff --git a/core/src/Model/Workflow/Values/fwd.hpp b/core/src/Model/Workflow/Values/fwd.hpp new file mode 100644 index 0000000..24f8119 --- /dev/null +++ b/core/src/Model/Workflow/Values/fwd.hpp @@ -0,0 +1,6 @@ +#pragma once + +// BasicValues.hpp +class NumericValue; +class TextValue; +class DateTimeValue; diff --git a/core/src/Model/Workflow/Workflow.cpp b/core/src/Model/Workflow/Workflow.cpp new file mode 100644 index 0000000..a32149e --- /dev/null +++ b/core/src/Model/Workflow/Workflow.cpp @@ -0,0 +1,396 @@ +#include "Workflow.hpp" + +#include +#include +#include +#include + +WorkflowConnection::WorkflowConnection() + : MultiConnections{} + , SingleConnection{ WorkflowNode::kInvalidId, -1 } + , ConnectionDirection{ OneToMany } { +} + +bool WorkflowConnection::IsValid() const { + return SingleConnection.Node != WorkflowNode::kInvalidId; +} + +std::span WorkflowConnection::GetSourcePoints() { + switch (ConnectionDirection) { + case ManyToOne: return MultiConnections; + case OneToMany: return { &SingleConnection, 1 }; + } +} + +std::span WorkflowConnection::GetSourcePoints() const { + switch (ConnectionDirection) { + case ManyToOne: return MultiConnections; + case OneToMany: return { &SingleConnection, 1 }; + } +} + +std::span WorkflowConnection::GetDestinationPoints() { + switch (ConnectionDirection) { + case ManyToOne: return { &SingleConnection, 1 }; + case OneToMany: return MultiConnections; + } +} + +std::span WorkflowConnection::GetDestinationPoints() const { + switch (ConnectionDirection) { + case ManyToOne: return { &SingleConnection, 1 }; + case OneToMany: return MultiConnections; + } +} + +bool WorkflowNode::InputPin::IsConstantConnection() const { + return ConnectionToConst && IsConnected(); +} + +bool WorkflowNode::InputPin::IsConnected() const { + return Connection != WorkflowConnection::kInvalidId; +} + +BaseValue::Kind WorkflowNode::InputPin::GetMatchingType() const { + return MatchingType; +} + +WorkflowConnection::Direction WorkflowNode::InputPin::GetSupportedDirection() const { + return AllowsMultipleConnections ? WorkflowConnection::ManyToOne : WorkflowConnection::OneToMany; +} + +bool WorkflowNode::OutputPin::IsConnected() const { + return Connection != WorkflowConnection::kInvalidId; +} + +BaseValue::Kind WorkflowNode::OutputPin::GetMatchingType() const { + return MatchingType; +} + +WorkflowConnection::Direction WorkflowNode::OutputPin::GetSupportedDirection() const { + return AllowsMultipleConnections ? WorkflowConnection::OneToMany : WorkflowConnection::ManyToOne; +} + +WorkflowNode::WorkflowNode(Type type, Kind kind) + : mType{ type } + , mKind{ kind } { +} + +size_t WorkflowNode::GetId() const { + return mId; +} + +WorkflowNode::Type WorkflowNode::GetType() const { + return mType; +} + +WorkflowNode::Kind WorkflowNode::GetKind() const { + return mKind; +} + +void WorkflowNode::ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId) { + mWorkflow->Connect(*this, nodeId, output, outputNodeId); +} + +void WorkflowNode::DisconnectInput(int nodeId) { + mWorkflow->DisconnectByDestination(*this, nodeId); +} + +bool WorkflowNode::IsInputConnected(int nodeId) const { + return mInputs[nodeId].IsConnected(); +} + +void WorkflowNode::ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId) { + mWorkflow->Connect(input, inputNodeId, *this, nodeId); +} + +void WorkflowNode::DisconnectOutput(int nodeId) { + mWorkflow->DisconnectBySource(*this, nodeId); +} + +bool WorkflowNode::IsOutputConnected(int nodeId) const { + return mOutputs[nodeId].IsConnected(); +} + +WorkflowNode::InputPin& WorkflowNode::InsertInputPin(int atIdx) { + assert(atIdx >= 0 && atIdx < mInputs.size()); + + mInputs.push_back(InputPin{}); + for (int i = (int)mInputs.size() - 1, end = atIdx + 1; i >= end; --i) { + SwapInputPin(i, i + 1); + } + + return mInputs[atIdx]; +} + +void WorkflowNode::RemoveInputPin(int pin) { + DisconnectInput(pin); + for (int i = 0, end = (int)mInputs.size() - 1; i < end; ++i) { + SwapInputPin(i, i + 1); + } + mInputs.resize(mInputs.size() - 1); +} + +void WorkflowNode::SwapInputPin(int a, int b) { + auto& pinA = mInputs[a]; + auto& pinB = mInputs[b]; + + if (mWorkflow) { + // Delay assignment to ConnectionPoint::Pin so that the pinB loop (if it happens to run and looks in the same Connection) doesn't match the point updated in the pinA loop + using Pt = WorkflowConnection::ConnectionPoint; + Pt* pointA = nullptr; + Pt* pointB = nullptr; + + if (pinA.IsConnected() && !pinA.IsConstantConnection()) { + auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetDestinationPoints(); + auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a }); + pointA = it == pts.end() ? nullptr : &*it; + } + if (pinB.IsConnected() && !pinB.IsConstantConnection()) { + auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetDestinationPoints(); + auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b }); + pointB = it == pts.end() ? nullptr : &*it; + } + + if (pointA) pointA->Pin = b; + if (pointB) pointB->Pin = a; + } + + std::swap(pinA, pinB); +} + +WorkflowNode::OutputPin& WorkflowNode::InsertOutputPin(int atIdx) { + assert(atIdx >= 0 && atIdx < mOutputs.size()); + + mOutputs.push_back(OutputPin{}); + for (int i = (int)mOutputs.size() - 1, end = atIdx + 1; i >= end; --i) { + SwapOutputPin(i, i + 1); + } + + return mOutputs[atIdx]; +} + +void WorkflowNode::RemoveOutputPin(int pin) { + DisconnectOutput(pin); + for (int i = 0, end = (int)mOutputs.size() - 1; i < end; ++i) { + SwapInputPin(i, i + 1); + } + mOutputs.resize(mOutputs.size() - 1); +} + +void WorkflowNode::SwapOutputPin(int a, int b) { + auto& pinA = mOutputs[a]; + auto& pinB = mOutputs[b]; + + if (mWorkflow) { + using Pt = WorkflowConnection::ConnectionPoint; + Pt* pointA = nullptr; + Pt* pointB = nullptr; + + if (pinA.IsConnected()) { + auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetSourcePoints(); + auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a }); + pointA = it == pts.end() ? nullptr : &*it; + } + if (pinB.IsConnected()) { + auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetSourcePoints(); + auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b }); + pointB = it == pts.end() ? nullptr : &*it; + } + + if (pointA) pointA->Pin = b; + if (pointB) pointB->Pin = a; + } + + std::swap(pinA, pinB); +} + +WorkflowConnection* Workflow::GetConnectionById(size_t id) { + return &mConnections[id]; +} + +WorkflowNode* Workflow::GetStepById(size_t id) { + return mNodes[id].get(); +} + +BaseValue* Workflow::GetConstantById(size_t id) { + return mConstants[id].get(); +} + +void Workflow::AddStep(std::unique_ptr step) { + auto [storage, id] = AllocWorkflowStep(); + storage = std::move(step); + storage->OnAttach(*this, id); + storage->mWorkflow = this; + storage->mId = id; +} + +void Workflow::RemoveStep(size_t id) { + auto& step = mNodes[id]; + if (step == nullptr) return; + + step->OnDetach(); + step->mWorkflow = nullptr; + step->mId = WorkflowNode::kInvalidId; +} + +bool Workflow::Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin) { + auto& src = sourceNode.mOutputs[sourcePin]; + auto& dst = destinationNode.mInputs[destinationPin]; + + // Equivalent to `if (o.GetSupportedDirection() != i.GetSupportedDirection()) return false;` + // Cannot connect two pins of different type + if (src.AllowsMultipleConnections == dst.AllowsMultipleConnections) return false; + // Would be same as `dst.GetSupportedDirection()` because we validated that they are the same above + auto direction = src.GetSupportedDirection(); + + // TODO report error to user? + if (src.GetMatchingType() != dst.GetMatchingType()) { + return false; + } + + using Pt = WorkflowConnection::ConnectionPoint; + Pt* srcConnPt; + Pt* dstConnPt; + size_t connId; + + switch (direction) { + case WorkflowConnection::ManyToOne: { + if (dst.IsConnected()) return false; + + WorkflowConnection* conn; + if (src.IsConnected()) { + conn = &mConnections[src.Connection]; + connId = src.Connection; + } else { + auto p = AllocWorkflowConnection(); + conn = &p.first; + connId = p.second; + } + + srcConnPt = &conn->MultiConnections.emplace_back(); + dstConnPt = &conn->SingleConnection; + } break; + + case WorkflowConnection::OneToMany: { + if (src.IsConnected()) return false; + + WorkflowConnection* conn; + if (dst.IsConnected()) { + conn = &mConnections[src.Connection]; + connId = src.Connection; + } else { + auto p = AllocWorkflowConnection(); + conn = &p.first; + connId = p.second; + } + + srcConnPt = &conn->SingleConnection; + dstConnPt = &conn->MultiConnections.emplace_back(); + } break; + } + + srcConnPt->Node = sourceNode.GetId(); + srcConnPt->Pin = sourcePin; + dstConnPt->Node = destinationNode.GetId(); + dstConnPt->Pin = destinationPin; + + src.Connection = connId; + dst.Connection = connId; + return true; +} + +bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, int sourcePin) { + auto& sp = sourceNode.mOutputs[sourcePin]; + if (!sp.IsConnected()) return false; + + auto& conn = mConnections[sp.Connection]; + + using Pt = WorkflowConnection::ConnectionPoint; + switch (sp.GetSupportedDirection()) { + case WorkflowConnection::ManyToOne: { + // Recessive pin, remove ConnectionPoint associated with this pin only + + auto& vec = conn.MultiConnections; + vec.erase(std::remove(vec.begin(), vec.end(), Pt{ sourceNode.GetId(), sourcePin }), vec.end()); + + sp.Connection = WorkflowConnection::kInvalidId; + } break; + + case WorkflowConnection::OneToMany: { + // Dominate pin, removes whole connection + + for (auto& pt : conn.MultiConnections) { + auto& node = *mNodes[pt.Node]; + node.mInputs[pt.Pin].Connection = WorkflowConnection::kInvalidId; + } + sp.Connection = WorkflowConnection::kInvalidId; + + conn = {}; + } break; + } + + return true; +} + +bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin) { + auto& dp = destinationNode.mInputs[destinationPin]; + if (!dp.IsConnected()) return false; + if (dp.IsConstantConnection()) { + dp.ConnectionToConst = false; + dp.Connection = WorkflowConnection::kInvalidId; + return true; + } + + auto& conn = mConnections[dp.Connection]; + + using Pt = WorkflowConnection::ConnectionPoint; + switch (dp.GetSupportedDirection()) { + case WorkflowConnection::ManyToOne: { + // Dominate pin, removes whole connection + + for (auto& pt : conn.MultiConnections) { + auto& node = *mNodes[pt.Node]; + node.mOutputs[pt.Pin].Connection = WorkflowConnection::kInvalidId; + } + dp.Connection = WorkflowConnection::kInvalidId; + + conn = {}; + } break; + + case WorkflowConnection::OneToMany: { + // Recessive pin, remove ConnectionPoint associated with this pin only + + auto& vec = conn.MultiConnections; + vec.erase(std::remove(vec.begin(), vec.end(), Pt{ destinationNode.GetId(), destinationPin }), vec.end()); + + dp.Connection = WorkflowConnection::kInvalidId; + } break; + } + + return true; +} + +std::pair Workflow::AllocWorkflowConnection() { + for (size_t idx = 0; idx < mConnections.size(); ++idx) { + auto& elm = mConnections[idx]; + if (!elm.IsValid()) { + return { elm, idx }; + } + } + + auto id = mConnections.size(); + return { mConnections.emplace_back(WorkflowConnection{}), id }; +} + +std::pair&, size_t> Workflow::AllocWorkflowStep() { + for (size_t idx = 0; idx < mNodes.size(); ++idx) { + auto& elm = mNodes[idx]; + if (elm == nullptr) { + return { elm, idx }; + } + } + + auto id = mNodes.size(); + return { mNodes.emplace_back(std::unique_ptr()), id }; +} diff --git a/core/src/Model/Workflow/Workflow.hpp b/core/src/Model/Workflow/Workflow.hpp new file mode 100644 index 0000000..a7b2c31 --- /dev/null +++ b/core/src/Model/Workflow/Workflow.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include "Value.hpp" +#include "cplt_fwd.hpp" + +#include +#include +#include +#include +#include +#include +#include + +class WorkflowConnection { +public: + static constexpr auto kInvalidId = std::numeric_limits::max(); + + enum Direction { + ManyToOne, + OneToMany, + }; + + struct ConnectionPoint { + size_t Node; + int Pin; + + bool operator==(const ConnectionPoint&) const = default; + }; + + std::vector MultiConnections; + ConnectionPoint SingleConnection; + Direction ConnectionDirection; + +public: + WorkflowConnection(); + + bool IsValid() const; + std::span GetSourcePoints(); + std::span GetSourcePoints() const; + std::span GetDestinationPoints(); + std::span GetDestinationPoints() const; +}; + +class WorkflowNode { +public: + static constexpr auto kInvalidId = std::numeric_limits::max(); + + enum Type { + InputType, + TransformType, + OutputType, + }; + + enum Kind { + KD_NumericAddition, + KD_NumericSubtraction, + KD_NumericMultiplication, + KD_NumericDivision, + KD_NumericExpression, + KD_TextFormatting, + KD_DocumentTemplateExpansion, + KD_FormInput, + KD_DatabaseRowsInput, + + InvalidKind, + KindCount = InvalidKind, + }; + +protected: + struct InputPin { + size_t Connection = WorkflowConnection::kInvalidId; + BaseValue::Kind MatchingType = BaseValue::KindInvalid; + bool ConnectionToConst = false; + bool AllowsMultipleConnections = false; + + /// A constant connection connects from a user-specified constant value, feeding to a valid \c Destination and \c DestinationPin (i.e. input pins). + bool IsConstantConnection() const; + bool IsConnected() const; + BaseValue::Kind GetMatchingType() const; + WorkflowConnection::Direction GetSupportedDirection() const; + }; + + struct OutputPin { + size_t Connection = WorkflowConnection::kInvalidId; + BaseValue::Kind MatchingType = BaseValue::KindInvalid; + bool AllowsMultipleConnections = false; + + bool IsConnected() const; + BaseValue::Kind GetMatchingType() const; + WorkflowConnection::Direction GetSupportedDirection() const; + }; + + friend class Workflow; + friend class WorkflowEvaluationContext; + + Workflow* mWorkflow; + size_t mId; + std::vector mInputs; + std::vector mOutputs; + Type mType; + Kind mKind; + +public: + WorkflowNode(Type type, Kind kind); + virtual ~WorkflowNode() = default; + + WorkflowNode(const WorkflowNode&) = delete; + WorkflowNode& operator=(const WorkflowNode&) = delete; + WorkflowNode(WorkflowNode&&) = default; + WorkflowNode& operator=(WorkflowNode&&) = default; + + size_t GetId() const; + Type GetType() const; + Kind GetKind() const; + + void ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId); + void DisconnectInput(int nodeId); + bool IsInputConnected(int nodeId) const; + + void ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId); + void DisconnectOutput(int nodeId); + bool IsOutputConnected(int nodeId) const; + + virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0; + +protected: + InputPin& InsertInputPin(int atIdx); + void RemoveInputPin(int pin); + void SwapInputPin(int a, int b); + OutputPin& InsertOutputPin(int atIdx); + void RemoveOutputPin(int pin); + void SwapOutputPin(int a, int b); + + /* For \c Workflow to invoke, override by implementations */ + + void OnAttach(Workflow& workflow, size_t newId) {} + void OnDetach() {} +}; + +class Workflow { +private: + friend class WorkflowEvaluationContext; + + std::vector mConnections; + std::vector> mNodes; + std::vector> mConstants; + +public: + WorkflowConnection* GetConnectionById(size_t id); + WorkflowNode* GetStepById(size_t id); + BaseValue* GetConstantById(size_t id); + + void AddStep(std::unique_ptr step); + void RemoveStep(size_t id); + + bool Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin); + bool DisconnectBySource(WorkflowNode& sourceNode, int sourcePin); + bool DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin); + +private: + std::pair AllocWorkflowConnection(); + std::pair&, size_t> AllocWorkflowStep(); +}; diff --git a/core/src/Model/Workflow/fwd.hpp b/core/src/Model/Workflow/fwd.hpp new file mode 100644 index 0000000..2323a91 --- /dev/null +++ b/core/src/Model/Workflow/fwd.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "Model/Workflow/Nodes/fwd.hpp" +#include "Model/Workflow/Values/fwd.hpp" + +// Value.hpp +class BaseValue; + +// Workflow.hpp +class WorkflowConnection; +class WorkflowNode; +class Workflow; +class WorkflowEvaluationError; +class WorkflowEvaluationContext; diff --git a/core/src/Model/WorkflowNodes.cpp b/core/src/Model/WorkflowNodes.cpp deleted file mode 100644 index f58c8bb..0000000 --- a/core/src/Model/WorkflowNodes.cpp +++ /dev/null @@ -1,312 +0,0 @@ -#include "WorkflowNodes.hpp" - -#include "Utils/Macros.hpp" -#include "Utils/RTTI.hpp" -#include "Utils/Variant.hpp" - -#include -#include -#include - -WorkflowNode::Kind NumericOperationNode::OperationTypeToNodeKind(OperationType type) { - switch (type) { - case Addition: return KD_NumericAddition; - case Subtraction: return KD_NumericSubtraction; - case Multiplication: return KD_NumericMultiplication; - case Division: return KD_NumericDivision; - default: UNREACHABLE; - } -} - -NumericOperationNode::OperationType NumericOperationNode::NodeKindToOperationType(Kind kind) { - switch (kind) { - case KD_NumericAddition: return Addition; - case KD_NumericSubtraction: return Subtraction; - case KD_NumericMultiplication: return Multiplication; - case KD_NumericDivision: return Division; - default: UNREACHABLE; - } -} - -bool NumericOperationNode::IsInstance(const WorkflowNode* node) { - return node->GetKind() >= KD_NumericAddition && node->GetKind() <= KD_NumericDivision; -} - -NumericOperationNode::NumericOperationNode(OperationType type) - : WorkflowNode(TransformType, OperationTypeToNodeKind(type)) - , mType{ type } { - mInputs.resize(2); - mInputs[0].MatchingType = BaseValue::KD_Numeric; - mInputs[1].MatchingType = BaseValue::KD_Numeric; - - mOutputs.resize(1); - mOutputs[0].MatchingType = BaseValue::KD_Numeric; -} - -void NumericOperationNode::Evaluate(WorkflowEvaluationContext& ctx) { - auto lhsVal = dyn_cast(ctx.GetConnectionValue(mInputs[0])); - if (!lhsVal) return; - double lhs = lhsVal->GetValue(); - - auto rhsVal = dyn_cast(ctx.GetConnectionValue(mInputs[1])); - if (!rhsVal) return; - double rhs = rhsVal->GetValue(); - - double res; - switch (mType) { - case Addition: res = lhs + rhs; break; - case Subtraction: res = lhs - rhs; break; - case Multiplication: res = lhs * rhs; break; - case Division: { - if (rhs == 0.0) { - // TODO localize - ctx.ReportError("Error: division by 0", *this); - return; - } - res = lhs / rhs; - } break; - - default: return; - } - - auto value = std::make_unique(); - value->SetValue(res); - ctx.SetConnectionValue(mOutputs[0], std::move(value)); -} - -bool NumericExpressionNode::IsInstance(const WorkflowNode* node) { - return node->GetKind() == KD_NumericExpression; -} - -NumericExpressionNode::NumericExpressionNode() - : WorkflowNode(TransformType, KD_NumericExpression) { -} - -struct TextFormatterNode::Argument { - ArgumentType ArgumentType; - int PinIdx; - - template - static void ForArguments(std::vector::iterator begin, std::vector::iterator end, const TFunction& func) { - for (auto it = begin; it != end; ++it) { - auto& elm = *it; - if (auto arg = std::get_if(&elm)) { - func(*arg); - } - } - } - - /// Find the pin index that the \c elmIdx -th element should have, based on the elements coming before it. - static int FindPinForElement(const std::vector& vec, int elmIdx) { - for (int i = elmIdx; i >= 0; --i) { - auto& elm = vec[i]; - if (auto arg = std::get_if(&elm)) { - return arg->PinIdx + 1; - } - } - return 0; - } -}; - -BaseValue::Kind TextFormatterNode::ArgumentTypeToValueKind(TextFormatterNode::ArgumentType arg) { - switch (arg) { - case NumericArgument: return BaseValue::KD_Numeric; - case TextArgument: return BaseValue::KD_Text; - case DateTimeArgument: return BaseValue::KD_DateTime; - default: UNREACHABLE; - } -} - -bool TextFormatterNode::IsInstance(const WorkflowNode* node) { - return node->GetKind() == KD_TextFormatting; -} - -TextFormatterNode::TextFormatterNode() - : WorkflowNode(TransformType, KD_TextFormatting) { -} - -int TextFormatterNode::GetElementCount() const { - return mElements.size(); -} - -const TextFormatterNode::Element& TextFormatterNode::GetElement(int idx) const { - return mElements[idx]; -} - -void TextFormatterNode::SetElement(int idx, std::string text) { - assert(idx >= 0 && idx < mElements.size()); - - std::visit( - Overloaded{ - [&](const std::string& original) { mMinOutputChars -= original.size(); }, - [&](const Argument& original) { PreRemoveElement(idx); }, - }, - mElements[idx]); - - mMinOutputChars += text.size(); - mElements[idx] = std::move(text); -} - -void TextFormatterNode::SetElement(int idx, ArgumentType argument) { - assert(idx >= 0 && idx < mElements.size()); - - std::visit( - Overloaded{ - [&](const std::string& original) { - mMinOutputChars -= original.size(); - - mElements[idx] = Argument{ - .ArgumentType = argument, - .PinIdx = Argument::FindPinForElement(mElements, idx), - }; - /* `original` is invalid from this point */ - }, - [&](const Argument& original) { - int pinIdx = original.PinIdx; - - // Create pin - auto& pin = mInputs[pinIdx]; - pin.MatchingType = ArgumentTypeToValueKind(argument); - - // Create element - mElements[idx] = Argument{ - .ArgumentType = argument, - .PinIdx = pinIdx, - }; - /* `original` is invalid from this point */ - }, - }, - mElements[idx]); -} - -void TextFormatterNode::InsertElement(int idx, std::string text) { - assert(idx >= 0); - if (idx >= mElements.size()) AppendElement(std::move(text)); - - mMinOutputChars += text.size(); - mElements.insert(mElements.begin() + idx, std::move(text)); -} - -void TextFormatterNode::InsertElement(int idx, ArgumentType argument) { - assert(idx >= 0); - if (idx >= mElements.size()) AppendElement(argument); - - int pinIdx = Argument::FindPinForElement(mElements, idx); - - // Create pin - auto& pin = InsertInputPin(pinIdx); - pin.MatchingType = ArgumentTypeToValueKind(argument); - - // Create element - mElements.insert( - mElements.begin() + idx, - Argument{ - .ArgumentType = argument, - .PinIdx = pinIdx, - }); -} - -void TextFormatterNode::AppendElement(std::string text) { - mMinOutputChars += text.size(); - mElements.push_back(std::move(text)); -} - -void TextFormatterNode::AppendElement(ArgumentType argument) { - int pinIdx = mInputs.size(); - // Create pin - mInputs.push_back(InputPin{}); - mInputs.back().MatchingType = ArgumentTypeToValueKind(argument); - // Creat eelement - mElements.push_back(Argument{ - .ArgumentType = argument, - .PinIdx = pinIdx, - }); -} - -void TextFormatterNode::RemoveElement(int idx) { - assert(idx >= 0 && idx < mElements.size()); - - PreRemoveElement(idx); - if (auto arg = std::get_if(&mElements[idx])) { - RemoveInputPin(arg->PinIdx); - } - mElements.erase(mElements.begin() + idx); -} - -void TextFormatterNode::Evaluate(WorkflowEvaluationContext& ctx) { - std::string result; - result.reserve((size_t)(mMinOutputChars * 1.5f)); - - auto HandleText = [&](const std::string& str) { - result += str; - }; - auto HandleArgument = [&](const Argument& arg) { - switch (arg.ArgumentType) { - case NumericArgument: { - if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { - result += val->GetString(); - } else { - // TODO localize - ctx.ReportError("Non-numeric value connected to a numeric text format parameter.", *this); - } - } break; - case TextArgument: { - if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { - result += val->GetValue(); - } else { - // TODO localize - ctx.ReportError("Non-text value connected to a textual text format parameter.", *this); - } - } break; - case DateTimeArgument: { - if (auto val = dyn_cast(ctx.GetConnectionValue(mInputs[arg.PinIdx]))) { - result += val->GetString(); - } else { - // TODO localize - ctx.ReportError("Non-date/time value connected to a date/time text format parameter.", *this); - } - } break; - } - }; - - for (auto& elm : mElements) { - std::visit(Overloaded{ HandleText, HandleArgument }, elm); - } -} - -void TextFormatterNode::PreRemoveElement(int idx) { - auto& elm = mElements[idx]; - if (auto arg = std::get_if(&elm)) { - RemoveInputPin(arg->PinIdx); - Argument::ForArguments( - mElements.begin() + idx + 1, - mElements.end(), - [&](Argument& arg) { - arg.PinIdx--; - }); - } -} - -bool DocumentTemplateExpansionNode::IsInstance(const WorkflowNode* node) { - return node->GetKind() == KD_DocumentTemplateExpansion; -} - -DocumentTemplateExpansionNode::DocumentTemplateExpansionNode() - : WorkflowNode(TransformType, KD_DocumentTemplateExpansion) { -} - -bool FormInputNode::IsInstance(const WorkflowNode* node) { - return node->GetKind() == KD_FormInput; -} - -FormInputNode::FormInputNode() - : WorkflowNode(InputType, KD_FormInput) { -} - -bool DatabaseRowsInputNode::IsInstance(const WorkflowNode* node) { - return node->GetKind() == KD_DatabaseRowsInput; -} - -DatabaseRowsInputNode::DatabaseRowsInputNode() - : WorkflowNode(InputType, KD_DatabaseRowsInput) { -} diff --git a/core/src/Model/WorkflowNodes.hpp b/core/src/Model/WorkflowNodes.hpp deleted file mode 100644 index 677158c..0000000 --- a/core/src/Model/WorkflowNodes.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include "Model/Workflow.hpp" - -#include -#include -#include -#include - -class NumericOperationNode : public WorkflowNode { -public: - enum OperationType { - Addition, - Subtraction, - Multiplication, - Division, - - InvalidType, - TypeCount = InvalidType, - }; - -private: - OperationType mType; - -public: - static Kind OperationTypeToNodeKind(OperationType type); - static OperationType NodeKindToOperationType(Kind kind); - static bool IsInstance(const WorkflowNode* node); - NumericOperationNode(OperationType type); - - virtual void Evaluate(WorkflowEvaluationContext& ctx) override; -}; - -class NumericExpressionNode : public WorkflowNode { -public: - static bool IsInstance(const WorkflowNode* node); - NumericExpressionNode(); - - // TODO -}; - -class TextFormatterNode : public WorkflowNode { -public: - enum ArgumentType { - NumericArgument, - TextArgument, - DateTimeArgument, - }; - -private: - struct Argument; - using Element = std::variant; - - std::vector mElements; - int mMinOutputChars; - -public: - static BaseValue::Kind ArgumentTypeToValueKind(ArgumentType arg); - static bool IsInstance(const WorkflowNode* node); - TextFormatterNode(); - - int GetElementCount() const; - const Element& GetElement(int idx) const; - - void SetElement(int idx, std::string text); - void SetElement(int idx, ArgumentType argument); - void InsertElement(int idx, std::string text); - void InsertElement(int idx, ArgumentType argument); - void AppendElement(std::string text); - void AppendElement(ArgumentType argument); - void RemoveElement(int idx); - - virtual void Evaluate(WorkflowEvaluationContext& ctx) override; - -private: - void PreRemoveElement(int idx); -}; - -class DocumentTemplateExpansionNode : public WorkflowNode { -public: - static bool IsInstance(const WorkflowNode* node); - DocumentTemplateExpansionNode(); - - // TODO -}; - -class FormInputNode : public WorkflowNode { -public: - static bool IsInstance(const WorkflowNode* node); - FormInputNode(); - - // TODO -}; - -class DatabaseRowsInputNode : public WorkflowNode { -public: - static bool IsInstance(const WorkflowNode* node); - DatabaseRowsInputNode(); - - // TODO -}; diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp index 5a75394..594599c 100644 --- a/core/src/Model/fwd.hpp +++ b/core/src/Model/fwd.hpp @@ -1,10 +1,6 @@ #pragma once -// EvaluatedValue.hpp -class BaseValue; -class NumericValue; -class TextValue; -class DateTimeValue; +#include "Model/Workflow/fwd.hpp" // Filter.hpp class TableRowsFilter; @@ -29,18 +25,3 @@ class SalesTable; class PurchasesTable; class DeliveryTable; class TransactionModel; - -// Workflow.hpp -class WorkflowConnection; -class WorkflowNode; -class Workflow; -class WorkflowEvaluationError; -class WorkflowEvaluationContext; - -// WorkflowSteps.hpp -class NumericOperationNode; -class NumericExpressionNode; -class TextFormatterNode; -class DocumentTemplateExpansionNode; -class FormInputNode; -class DatabaseRowsInputNode; diff --git a/core/src/UI/UI_Workflows.cpp b/core/src/UI/UI_Workflows.cpp index 101fa7b..0adfdc2 100644 --- a/core/src/UI/UI_Workflows.cpp +++ b/core/src/UI/UI_Workflows.cpp @@ -1,11 +1,16 @@ #include "UI.hpp" -#include "Model/Workflow.hpp" -#include "Model/WorkflowNodes.hpp" +#include "Model/Workflow/Nodes/DocumentNodes.hpp" +#include "Model/Workflow/Nodes/NumericNodes.hpp" +#include "Model/Workflow/Nodes/TextNodes.hpp" +#include "Model/Workflow/Nodes/UserInputNodes.hpp" +#include "Model/Workflow/Workflow.hpp" #include "UI/Localization.hpp" +#include "Utils/Macros.hpp" #include #include +#include #include namespace { @@ -13,12 +18,41 @@ class WorkflowCreationMenu { private: using WorkflowNodeConstructor = std::unique_ptr (*)(); + enum Category { + NumericCategory, + TextCategory, + DocumentsCategory, + UserInputCategory, + SystemInputCategory, + OutputCategory, + }; + struct Candidate { WorkflowNodeConstructor Constructor; + std::string Name; + Category Category; }; std::vector mCandidates; +#define SUB_RANGE_ACCESS(Type, AccessorName, storage, begin, nextBegin) \ + std::span AccessorName() { return { &storage[begin], (size_t)(nextBegin - begin) }; } + + int mTextOffset; + int mDocumentOffset; + int mUserInputOffset; + int mSystemInputNodes; + int mOutputOffset; + + SUB_RANGE_ACCESS(Candidate, GetNumericNodes, mCandidates, 0, mTextOffset); + SUB_RANGE_ACCESS(Candidate, GetTextNodes, mCandidates, mTextOffset, mDocumentOffset); + SUB_RANGE_ACCESS(Candidate, GetDocumentNodes, mCandidates, mDocumentOffset, mUserInputOffset); + SUB_RANGE_ACCESS(Candidate, GetUserInputNodes, mCandidates, mUserInputOffset, mSystemInputNodes); + SUB_RANGE_ACCESS(Candidate, GetSystemInputNodes, mCandidates, mSystemInputNodes, mOutputOffset); + SUB_RANGE_ACCESS(Candidate, GetOutputNodes, mCandidates, mOutputOffset, mCandidates.size()); + +#undef SUB_RANGE_ACCESS + public: WorkflowCreationMenu() { SetupCandidates(); @@ -26,41 +60,67 @@ public: private: void SetupCandidates() { + // Numeric nodes offset start at 0 mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr { return std::make_unique(NumericOperationNode::Addition); }, + .Name = "Add", + .Category = NumericCategory, }); - mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr { return std::make_unique(NumericOperationNode::Subtraction); }, + .Name = "Subtract", + .Category = NumericCategory, }); - mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr { return std::make_unique(NumericOperationNode::Multiplication); }, + .Name = "Multiply", + .Category = NumericCategory, }); - mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr { return std::make_unique(NumericOperationNode::Division); }, + .Name = "Divide", + .Category = NumericCategory, + }); + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, + .Name = "Evaluate expression", + .Category = NumericCategory, }); -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, -// }); - + mTextOffset = mCandidates.size(); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, + .Name = "Fill template text", + .Category = TextCategory, + }); + + mDocumentOffset = mCandidates.size(); + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, + .Name = "Document template", + .Category = Category::DocumentsCategory, + }); + + /* Inputs */ + + mUserInputOffset = mCandidates.size(); + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, + .Name = "Input: form", + .Category = Category::UserInputCategory, + }); + + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, + .Name = "Input: database rows", + .Category = Category::UserInputCategory, }); -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, -// }); + mSystemInputNodes = mCandidates.size(); -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, -// }); + /* Outputs */ -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr { return std::make_unique(); }, -// }); + mOutputOffset = mCandidates.size(); } }; -- cgit v1.2.3-70-g09d2