diff options
author | rtk0c <[email protected]> | 2021-04-19 14:00:47 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2021-04-19 14:00:47 -0700 |
commit | 1e09caaa2980fe901453b4b90985967a51157887 (patch) | |
tree | df61974f9a5efa9a6732bd6d7b1ec1e6d1af182a /core | |
parent | b00b306de1140cb7b759ed0f639e8210fd7dffa6 (diff) |
Split workflow into multiple files, fix unity build
Diffstat (limited to 'core')
23 files changed, 683 insertions, 525 deletions
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/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 <queue> + +namespace { +enum class EvaluationStatus { + Unevaluated, + Success, + Failed, +}; +} // namespace + +struct WorkflowEvaluationContext::RuntimeNode { + EvaluationStatus Status = EvaluationStatus::Unevaluated; +}; + +struct WorkflowEvaluationContext::RuntimeConnection { + std::unique_ptr<BaseValue> 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<BaseValue> value) { + mRuntimeConnections[id].Value = std::move(value); +} + +void WorkflowEvaluationContext::SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr<BaseValue> value) { + if (outputPin.IsConnected()) { + SetConnectionValue(outputPin.Connection, std::move(value)); + } +} + +void WorkflowEvaluationContext::Run() { + std::queue<size_t> 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 <cstddef> +#include <cstdint> +#include <string> +#include <vector> + +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<RuntimeNode> mRuntimeNodes; + std::vector<RuntimeConnection> mRuntimeConnections; + std::vector<WorkflowEvaluationError> mErrors; + std::vector<WorkflowEvaluationError> 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<BaseValue> value); + void SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr<BaseValue> 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 <cassert> +#include <utility> + +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<NumericValue>(ctx.GetConnectionValue(mInputs[0])); + if (!lhsVal) return; + double lhs = lhsVal->GetValue(); + + auto rhsVal = dyn_cast<NumericValue>(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<NumericValue>(); + 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 <cstddef> +#include <memory> +#include <variant> +#include <vector> + +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/WorkflowNodes.cpp b/core/src/Model/Workflow/Nodes/TextNodes.cpp index f58c8bb..3852c66 100644 --- a/core/src/Model/WorkflowNodes.cpp +++ b/core/src/Model/Workflow/Nodes/TextNodes.cpp @@ -1,5 +1,7 @@ -#include "WorkflowNodes.hpp" +#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" @@ -7,85 +9,10 @@ #include <cassert> #include <utility> #include <variant> +#include <vector> -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<NumericValue>(ctx.GetConnectionValue(mInputs[0])); - if (!lhsVal) return; - double lhs = lhsVal->GetValue(); - - auto rhsVal = dyn_cast<NumericValue>(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<NumericValue>(); - 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; - +class TextFormatterNode::Impl { +public: template <class TFunction> static void ForArguments(std::vector<Element>::iterator begin, std::vector<Element>::iterator end, const TFunction& func) { for (auto it = begin; it != end; ++it) { @@ -157,7 +84,7 @@ void TextFormatterNode::SetElement(int idx, ArgumentType argument) { mElements[idx] = Argument{ .ArgumentType = argument, - .PinIdx = Argument::FindPinForElement(mElements, idx), + .PinIdx = Impl::FindPinForElement(mElements, idx), }; /* `original` is invalid from this point */ }, @@ -191,7 +118,7 @@ void TextFormatterNode::InsertElement(int idx, ArgumentType argument) { assert(idx >= 0); if (idx >= mElements.size()) AppendElement(argument); - int pinIdx = Argument::FindPinForElement(mElements, idx); + int pinIdx = Impl::FindPinForElement(mElements, idx); // Create pin auto& pin = InsertInputPin(pinIdx); @@ -278,35 +205,11 @@ void TextFormatterNode::PreRemoveElement(int idx) { auto& elm = mElements[idx]; if (auto arg = std::get_if<Argument>(&elm)) { RemoveInputPin(arg->PinIdx); - Argument::ForArguments( + Impl::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) { -} +}
\ 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 <cstddef> +#include <memory> +#include <variant> +#include <vector> + +class TextFormatterNode : public WorkflowNode { +public: + enum ArgumentType { + NumericArgument, + TextArgument, + DateTimeArgument, + }; + +private: + class Impl; + + struct Argument { + ArgumentType ArgumentType; + int PinIdx; + }; + using Element = std::variant<std::string, Argument>; + + std::vector<Element> 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/EvaluatedValue.cpp b/core/src/Model/Workflow/Values/BasicValues.cpp index 685d50f..fd70acd 100644 --- a/core/src/Model/EvaluatedValue.cpp +++ b/core/src/Model/Workflow/Values/BasicValues.cpp @@ -1,15 +1,7 @@ -#include "EvaluatedValue.hpp" +#include "BasicValues.hpp" #include <charconv> -BaseValue::BaseValue(Kind kind) - : mKind{ kind } { -} - -BaseValue::Kind BaseValue::GetKind() const { - return mKind; -} - bool NumericValue::IsInstance(const BaseValue* value) { return value->GetKind() == KD_Numeric; } diff --git a/core/src/Model/EvaluatedValue.hpp b/core/src/Model/Workflow/Values/BasicValues.hpp index 880fd3e..a116c8c 100644 --- a/core/src/Model/EvaluatedValue.hpp +++ b/core/src/Model/Workflow/Values/BasicValues.hpp @@ -1,36 +1,11 @@ #pragma once +#include "Model/Workflow/Value.hpp" + #include <chrono> #include <cstdint> #include <string> -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; 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.cpp b/core/src/Model/Workflow/Workflow.cpp index 7ca1b9e..a32149e 100644 --- a/core/src/Model/Workflow.cpp +++ b/core/src/Model/Workflow/Workflow.cpp @@ -394,183 +394,3 @@ std::pair<std::unique_ptr<WorkflowNode>&, size_t> Workflow::AllocWorkflowStep() auto id = mNodes.size(); return { mNodes.emplace_back(std::unique_ptr<WorkflowNode>()), id }; } - -namespace { -enum class EvaluationStatus { - Unevaluated, - Success, - Failed, -}; -} // namespace - -struct WorkflowEvaluationContext::RuntimeNode { - EvaluationStatus Status = EvaluationStatus::Unevaluated; -}; - -struct WorkflowEvaluationContext::RuntimeConnection { - std::unique_ptr<BaseValue> 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<BaseValue> value) { - mRuntimeConnections[id].Value = std::move(value); -} - -void WorkflowEvaluationContext::SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr<BaseValue> value) { - if (outputPin.IsConnected()) { - SetConnectionValue(outputPin.Connection, std::move(value)); - } -} - -void WorkflowEvaluationContext::Run() { - std::queue<size_t> 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/Workflow.hpp index f1459ba..a7b2c31 100644 --- a/core/src/Model/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Model/EvaluatedValue.hpp" +#include "Value.hpp" #include "cplt_fwd.hpp" #include <cstddef> @@ -8,6 +8,7 @@ #include <limits> #include <memory> #include <span> +#include <string> #include <vector> class WorkflowConnection { @@ -160,52 +161,3 @@ private: std::pair<WorkflowConnection&, size_t> AllocWorkflowConnection(); std::pair<std::unique_ptr<WorkflowNode>&, 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<RuntimeNode> mRuntimeNodes; - std::vector<RuntimeConnection> mRuntimeConnections; - std::vector<WorkflowEvaluationError> mErrors; - std::vector<WorkflowEvaluationError> 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<BaseValue> value); - void SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr<BaseValue> 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/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.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 <cstddef> -#include <memory> -#include <variant> -#include <vector> - -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::string, Argument>; - - std::vector<Element> 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 <imgui.h> #include <memory> +#include <span> #include <vector> namespace { @@ -13,12 +18,41 @@ class WorkflowCreationMenu { private: using WorkflowNodeConstructor = std::unique_ptr<WorkflowNode> (*)(); + enum Category { + NumericCategory, + TextCategory, + DocumentsCategory, + UserInputCategory, + SystemInputCategory, + OutputCategory, + }; + struct Candidate { WorkflowNodeConstructor Constructor; + std::string Name; + Category Category; }; std::vector<Candidate> mCandidates; +#define SUB_RANGE_ACCESS(Type, AccessorName, storage, begin, nextBegin) \ + std::span<Type> 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<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Addition); }, + .Name = "Add", + .Category = NumericCategory, }); - mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Subtraction); }, + .Name = "Subtract", + .Category = NumericCategory, }); - mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Multiplication); }, + .Name = "Multiply", + .Category = NumericCategory, }); - mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericOperationNode>(NumericOperationNode::Division); }, + .Name = "Divide", + .Category = NumericCategory, + }); + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericExpressionNode>(); }, + .Name = "Evaluate expression", + .Category = NumericCategory, }); -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<NumericExpressionNode>(); }, -// }); - + mTextOffset = mCandidates.size(); mCandidates.push_back(Candidate{ .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<TextFormatterNode>(); }, + .Name = "Fill template text", + .Category = TextCategory, + }); + + mDocumentOffset = mCandidates.size(); + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<DocumentTemplateExpansionNode>(); }, + .Name = "Document template", + .Category = Category::DocumentsCategory, + }); + + /* Inputs */ + + mUserInputOffset = mCandidates.size(); + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<FormInputNode>(); }, + .Name = "Input: form", + .Category = Category::UserInputCategory, + }); + + mCandidates.push_back(Candidate{ + .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<DatabaseRowsInputNode>(); }, + .Name = "Input: database rows", + .Category = Category::UserInputCategory, }); -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<DocumentTemplateExpansionNode>(); }, -// }); + mSystemInputNodes = mCandidates.size(); -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<FormInputNode>(); }, -// }); + /* Outputs */ -// mCandidates.push_back(Candidate{ -// .Constructor = []() -> std::unique_ptr<WorkflowNode> { return std::make_unique<DatabaseRowsInputNode>(); }, -// }); + mOutputOffset = mCandidates.size(); } }; |