aboutsummaryrefslogtreecommitdiff
path: root/core/src/Model
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-04-16 16:49:28 -0700
committerrtk0c <[email protected]>2021-04-16 16:49:28 -0700
commit4e5730e1fcef150ce2f13f52a278890589ca96ad (patch)
tree0fe4002349047c7c770754e273d6a1d1ed666cbb /core/src/Model
parent80d8ae5a6fef6c9a34e81e240539cb655dd99851 (diff)
More work on workflows
- WorkflowStep -> WorkflowNode - Added initial kinds of WorkflowNode's
Diffstat (limited to 'core/src/Model')
-rw-r--r--core/src/Model/EvaluatedValue.cpp49
-rw-r--r--core/src/Model/EvaluatedValue.hpp25
-rw-r--r--core/src/Model/Workflow.cpp306
-rw-r--r--core/src/Model/Workflow.hpp175
-rw-r--r--core/src/Model/WorkflowNodes.cpp309
-rw-r--r--core/src/Model/WorkflowNodes.hpp101
-rw-r--r--core/src/Model/WorkflowSteps.cpp11
-rw-r--r--core/src/Model/WorkflowSteps.hpp44
-rw-r--r--core/src/Model/fwd.hpp15
9 files changed, 861 insertions, 174 deletions
diff --git a/core/src/Model/EvaluatedValue.cpp b/core/src/Model/EvaluatedValue.cpp
index c86f00b..685d50f 100644
--- a/core/src/Model/EvaluatedValue.cpp
+++ b/core/src/Model/EvaluatedValue.cpp
@@ -1,15 +1,32 @@
#include "EvaluatedValue.hpp"
-BaseValue::BaseValue(Type type)
- : mType{ type } {
+#include <charconv>
+
+BaseValue::BaseValue(Kind kind)
+ : mKind{ kind } {
+}
+
+BaseValue::Kind BaseValue::GetKind() const {
+ return mKind;
}
-BaseValue::Type BaseValue::GetType() const {
- return mType;
+bool NumericValue::IsInstance(const BaseValue* value) {
+ return value->GetKind() == KD_Numeric;
}
NumericValue::NumericValue()
- : BaseValue(BaseValue::NumericType) {
+ : 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 "<err>";
+ }
}
int64_t NumericValue::GetInt() const {
@@ -24,8 +41,12 @@ void NumericValue::SetValue(double value) {
mValue = value;
}
+bool TextValue::IsInstance(const BaseValue* value) {
+ return value->GetKind() == KD_Text;
+}
+
TextValue::TextValue()
- : BaseValue(BaseValue::TextType) {
+ : BaseValue(BaseValue::KD_Text) {
}
const std::string& TextValue::GetValue() const {
@@ -36,8 +57,22 @@ void TextValue::SetValue(const std::string& value) {
mValue = value;
}
+bool DateTimeValue::IsInstance(const BaseValue* value) {
+ return value->GetKind() == KD_DateTime;
+}
+
DateTimeValue::DateTimeValue()
- : BaseValue(BaseValue::DateTimeType) {
+ : 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<std::chrono::system_clock>& DateTimeValue::GetValue() const {
diff --git a/core/src/Model/EvaluatedValue.hpp b/core/src/Model/EvaluatedValue.hpp
index 838fc39..880fd3e 100644
--- a/core/src/Model/EvaluatedValue.hpp
+++ b/core/src/Model/EvaluatedValue.hpp
@@ -6,21 +6,21 @@
class BaseValue {
public:
- enum Type {
- NumericType,
- TextType,
- DateTimeType,
+ enum Kind {
+ KD_Numeric,
+ KD_Text,
+ KD_DateTime,
/// An unspecified type, otherwise known as "any" in some contexts.
- InvalidType,
- TypeCount = InvalidType,
+ KindInvalid,
+ KindCount = KindInvalid,
};
private:
- Type mType;
+ Kind mKind;
public:
- BaseValue(Type type);
+ BaseValue(Kind kind);
virtual ~BaseValue() = default;
BaseValue(const BaseValue&) = delete;
@@ -28,7 +28,7 @@ public:
BaseValue(BaseValue&&) = default;
BaseValue& operator=(BaseValue&&) = default;
- Type GetType() const;
+ Kind GetKind() const;
};
class NumericValue : public BaseValue {
@@ -36,8 +36,10 @@ 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);
@@ -48,7 +50,9 @@ private:
std::string mValue;
public:
+ static bool IsInstance(const BaseValue* value);
TextValue();
+
const std::string& GetValue() const;
void SetValue(const std::string& value);
};
@@ -58,7 +62,10 @@ private:
std::chrono::time_point<std::chrono::system_clock> mValue;
public:
+ static bool IsInstance(const BaseValue* value);
DateTimeValue();
+
+ std::string GetString() const;
const std::chrono::time_point<std::chrono::system_clock>& GetValue() const;
void SetValue(const std::chrono::time_point<std::chrono::system_clock>& value);
};
diff --git a/core/src/Model/Workflow.cpp b/core/src/Model/Workflow.cpp
index 96031c1..385544e 100644
--- a/core/src/Model/Workflow.cpp
+++ b/core/src/Model/Workflow.cpp
@@ -1,98 +1,298 @@
#include "Workflow.hpp"
-bool WorkflowStep::InputNode::IsConnected() const {
- return ConnectedNodeIndex != -1;
+#include <cassert>
+#include <utility>
+
+WorkflowConnection::WorkflowConnection()
+ : Source{ WorkflowNode::kInvalidId }
+ , Destination{ WorkflowNode::kInvalidId }
+ , SourcePin{ -1 }
+ , DestinationPin{ -1 } {
+}
+
+bool WorkflowConnection::IsValid() const {
+ return Source != WorkflowNode::kInvalidId;
+}
+
+bool WorkflowNode::InputPin::IsConstantConnection() const {
+ return ConnectionToConst;
}
-bool WorkflowStep::OutputNode::IsConnected() const {
- return ConnectedNodeIndex != -1;
+bool WorkflowNode::InputPin::IsConnected() const {
+ return Connection != WorkflowConnection::kInvalidId;
}
-size_t WorkflowStep::GetId() const {
+bool WorkflowNode::OutputPin::IsConnected() const {
+ return Connection != WorkflowConnection::kInvalidId;
+}
+
+WorkflowNode::WorkflowNode(Type type, Kind kind)
+ : mType{ type }
+ , mKind{ kind } {
+}
+
+size_t WorkflowNode::GetId() const {
return mId;
}
-void WorkflowStep::ConnectInput(int nodeId, size_t outputId, int outputNodeId) {
- mWorkflow->Connect(mId, nodeId, outputId, outputNodeId);
+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 WorkflowStep::DisconnectInput(int nodeId) {
- mWorkflow->DisconnectByDestination(mId, nodeId);
+void WorkflowNode::DisconnectInput(int nodeId) {
+ mWorkflow->DisconnectByDestination(*this, nodeId);
}
-bool WorkflowStep::IsInputConnected(int nodeId) const {
+bool WorkflowNode::IsInputConnected(int nodeId) const {
return mInputs[nodeId].IsConnected();
}
-void WorkflowStep::ConnectOutput(int nodeId, size_t inputId, int inputNodeId) {
- mWorkflow->Connect(inputId, inputNodeId, mId, nodeId);
+void WorkflowNode::ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId) {
+ mWorkflow->Connect(input, inputNodeId, *this, nodeId);
}
-void WorkflowStep::DisconnectOutput(int nodeId) {
- mWorkflow->DisconnectBySource(mId, nodeId);
+void WorkflowNode::DisconnectOutput(int nodeId) {
+ mWorkflow->DisconnectBySource(*this, nodeId);
}
-bool WorkflowStep::IsOutputConnected(int nodeId) const {
+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) {
+ if (pinA.IsConnected() && !pinA.IsConstantConnection()) {
+ auto& conn = *mWorkflow->GetConnectionById(pinA.Connection);
+ conn.DestinationPin = b;
+ }
+ if (pinB.IsConnected() && !pinB.IsConstantConnection()) {
+ auto& conn = *mWorkflow->GetConnectionById(pinB.Connection);
+ conn.DestinationPin = 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) {
+ if (pinA.IsConnected()) {
+ auto& conn = *mWorkflow->GetConnectionById(pinA.Connection);
+ conn.SourcePin = b;
+ }
+ if (pinB.IsConnected()) {
+ auto& conn = *mWorkflow->GetConnectionById(pinB.Connection);
+ conn.SourcePin = a;
+ }
+ }
+
+ std::swap(pinA, pinB);
+}
+
WorkflowConnection* Workflow::GetConnectionById(size_t id) {
return &mConnections[id];
}
-WorkflowStep* Workflow::GetStepById(size_t id) {
- return mSteps[id].get();
+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<WorkflowNode> 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;
+}
+
+void Workflow::Connect(WorkflowNode& source, int sourceNode, WorkflowNode& destination, int destinationNode) {
+ if (source.mInputs[sourceNode].IsConnected()) {
+ DisconnectBySource(source, sourceNode);
+ }
+
+ auto [conn, id] = AllocWorkflowConnection();
+ conn.Source = source.GetId();
+ conn.SourcePin = sourceNode;
+ conn.Destination = destination.GetId();
+ conn.DestinationPin = destinationNode;
+
+ source.mInputs[sourceNode].Connection = id;
+ destination.mInputs[destinationNode].Connection = id;
+}
+
+void Workflow::DisconnectBySource(WorkflowNode& source, int sourceNode) {
+ auto& sn = source.mOutputs[sourceNode];
+ if (!sn.IsConnected()) return;
+
+ auto& conn = mConnections[sn.Connection];
+ auto& dn = mNodes[conn.Destination]->mInputs[conn.DestinationPin];
+
+ sn.Connection = WorkflowConnection::kInvalidId;
+ dn.Connection = WorkflowConnection::kInvalidId;
+ conn = {};
+}
+
+void Workflow::DisconnectByDestination(WorkflowNode& destination, int destinationNode) {
+ auto& dn = destination.mOutputs[destinationNode];
+ if (!dn.IsConnected()) return;
+
+ auto& conn = mConnections[dn.Connection];
+ auto& sn = mNodes[conn.Source]->mInputs[conn.SourcePin];
+
+ sn.Connection = WorkflowConnection::kInvalidId;
+ dn.Connection = WorkflowConnection::kInvalidId;
+ conn = {};
+}
+
+std::pair<WorkflowConnection&, size_t> 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<std::unique_ptr<WorkflowNode>&, 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<WorkflowNode>()), id };
}
-void WorkflowStep::OnIOAboutToChange() {
- // TODO more robust solution that handles changes incrementally
- for (size_t i = 0; i < mInputs.size(); ++i) {
- DisconnectInput(i);
+struct WorkflowEvaluationContext::RuntimeNode {
+ WorkflowNode* Node;
+};
+
+struct WorkflowEvaluationContext::RuntimeConnection {
+ WorkflowConnection* Connection;
+ std::unique_ptr<BaseValue> Value;
+};
+
+WorkflowEvaluationContext::WorkflowEvaluationContext(Workflow& workflow)
+ : mWorkflow{ &workflow } {
+ mNodes.resize(workflow.mNodes.size());
+ for (size_t i = 0; i < workflow.mNodes.size(); ++i) {
+ mNodes[i].Node = workflow.mNodes[i].get();
}
- for (size_t i = 0; i < mOutputs.size(); ++i) {
- DisconnectOutput(i);
+
+ mConnections.resize(workflow.mConnections.size());
+ for (size_t i = 0; i < workflow.mConnections.size(); ++i) {
+ mConnections[i].Connection = &workflow.mConnections[i];
}
}
-void WorkflowStep::OnIOChanged() {
+BaseValue* WorkflowEvaluationContext::GetConnectionValue(size_t id, bool constant) {
+ if (constant) {
+ return mWorkflow->GetConstantById(id);
+ } else {
+ return mConnections[id].Value.get();
+ }
}
-void Workflow::Connect(size_t source, int sourceNode, size_t destination, int destinationNode) {
- auto& src = *mSteps[source];
- auto& o = src.mInputs[sourceNode];
- if (o.IsConnected()) {
- DisconnectBySource(source, sourceNode);
+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) {
+ mConnections[id].Value = std::move(value);
+}
- o.ConnectedStep = destination;
- o.ConnectedNodeIndex = destinationNode;
+void WorkflowEvaluationContext::SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr<BaseValue> value) {
+ if (outputPin.IsConnected()) {
+ SetConnectionValue(outputPin.Connection, std::move(value));
+ }
+}
- auto& dst = *mSteps[destination];
- auto& i = dst.mInputs[destinationNode];
- i.ConnectedStep = source;
- i.ConnectedNodeIndex = sourceNode;
+void WorkflowEvaluationContext::Run() {
+ // TODO
}
-void Workflow::DisconnectBySource(size_t source, int sourceNode) {
- auto& src = *mSteps[source];
- auto& o = src.mOutputs[sourceNode];
- if (!o.IsConnected()) return;
+void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node, int pinId, bool inputPin) {
+}
- auto& i = mSteps[o.ConnectedStep]->mInputs[o.ConnectedNodeIndex];
- i.ConnectedStep = WorkflowStep::kInvalidId;
- i.ConnectedNodeIndex = -1;
- o.ConnectedStep = WorkflowStep::kInvalidId;
- o.ConnectedNodeIndex = -1;
+void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node) {
}
-void Workflow::DisconnectByDestination(size_t destination, int destinationNode) {
- auto& dst = *mSteps[destination];
- auto& i = dst.mInputs[destinationNode];
- if (!i.IsConnected()) return;
+void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node, int pinId, bool inputPin) {
+}
- auto& o = mSteps[i.ConnectedStep]->mOutputs[i.ConnectedNodeIndex];
- i.ConnectedStep = WorkflowStep::kInvalidId;
- i.ConnectedNodeIndex = -1;
- o.ConnectedStep = WorkflowStep::kInvalidId;
- o.ConnectedNodeIndex = -1;
+void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node) {
}
diff --git a/core/src/Model/Workflow.hpp b/core/src/Model/Workflow.hpp
index a90ef21..2c83816 100644
--- a/core/src/Model/Workflow.hpp
+++ b/core/src/Model/Workflow.hpp
@@ -1,6 +1,7 @@
#pragma once
-#include "EvaluatedValue.hpp"
+#include "Model/EvaluatedValue.hpp"
+#include "cplt_fwd.hpp"
#include <cstddef>
#include <cstdint>
@@ -10,92 +11,180 @@
class WorkflowConnection {
public:
+ static constexpr auto kInvalidId = std::numeric_limits<size_t>::max();
+
size_t Source;
size_t Destination;
+ int SourcePin;
+ int DestinationPin;
+
+public:
+ WorkflowConnection();
+
+ bool IsValid() const;
};
-class WorkflowStep {
+class WorkflowNode {
public:
static constexpr auto kInvalidId = std::numeric_limits<size_t>::max();
- // TODO do we even need this?
- // enum Type {
- // NumericAdditionType,
- // NumericSubtractionType,
- // NumericMultiplicationType,
- // NumericDivisionType,
- // NumericExpressionType,
- // TextFormattingType,
- // FormInputType,
- // DatabaseRowsInputType,
- // DocumentTemplateExpansionType,
- //
- // InvalidType,
- // TypeCount = InvalidType,
- // };
+ 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 InputNode {
- size_t ConnectedStep = kInvalidId;
- int ConnectedNodeIndex = -1;
- BaseValue::Type MatchingType = BaseValue::InvalidType;
+ struct InputPin {
+ size_t Connection = WorkflowConnection::kInvalidId;
+ BaseValue::Kind MatchingType = BaseValue::KindInvalid;
+ bool ConnectionToConst = 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;
};
- struct OutputNode {
- size_t ConnectedStep = kInvalidId;
- int ConnectedNodeIndex = -1;
- BaseValue::Type MatchingType = BaseValue::InvalidType;
+ struct OutputPin {
+ size_t Connection = WorkflowConnection::kInvalidId;
+ BaseValue::Kind MatchingType = BaseValue::KindInvalid;
bool IsConnected() const;
};
friend class Workflow;
+ friend class WorkflowEvaluationError;
+ friend class WorkflowEvaluationContext;
+
Workflow* mWorkflow;
size_t mId;
- std::vector<InputNode> mInputs;
- std::vector<OutputNode> mOutputs;
+ std::vector<InputPin> mInputs;
+ std::vector<OutputPin> mOutputs;
+ Type mType;
+ Kind mKind;
public:
- virtual ~WorkflowStep() = default;
+ WorkflowNode(Type type, Kind kind);
+ virtual ~WorkflowNode() = default;
- WorkflowStep(const WorkflowStep&) = delete;
- WorkflowStep& operator=(const WorkflowStep&) = delete;
- WorkflowStep(WorkflowStep&&) = default;
- WorkflowStep& operator=(WorkflowStep&&) = 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, size_t outputId, int outputNodeId);
+ void ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId);
void DisconnectInput(int nodeId);
bool IsInputConnected(int nodeId) const;
- void ConnectOutput(int nodeId, size_t inputId, int inputNodeId);
+ void ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId);
void DisconnectOutput(int nodeId);
bool IsOutputConnected(int nodeId) const;
+ virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0;
+
protected:
- /// Child classes should call this whenever \c mInputs and \c mOutputs are about to be modified (append or remove)
- /// after invocation of the constructor.
- void OnIOAboutToChange();
- void OnIOChanged();
+ 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<WorkflowConnection> mConnections;
- std::vector<std::unique_ptr<WorkflowStep>> mSteps;
+ std::vector<std::unique_ptr<WorkflowNode>> mNodes;
+ std::vector<std::unique_ptr<BaseValue>> mConstants;
public:
WorkflowConnection* GetConnectionById(size_t id);
- WorkflowStep* GetStepById(size_t id);
+ WorkflowNode* GetStepById(size_t id);
+ BaseValue* GetConstantById(size_t id);
+
+ void AddStep(std::unique_ptr<WorkflowNode> step);
+ void RemoveStep(size_t id);
+
+ void Connect(WorkflowNode& source, int sourceNode, WorkflowNode& destination, int destinationNode);
+ void DisconnectBySource(WorkflowNode& source, int sourceNode);
+ void DisconnectByDestination(WorkflowNode& destination, int destinationNode);
- void Connect(size_t source, int sourceNode, size_t destination, int destinationNode);
- void DisconnectBySource(size_t source, int sourceNode);
- void DisconnectByDestination(size_t destination, int destinationNode);
+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> mNodes;
+ std::vector<RuntimeConnection> mConnections;
+ 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/WorkflowNodes.cpp b/core/src/Model/WorkflowNodes.cpp
new file mode 100644
index 0000000..8c315b6
--- /dev/null
+++ b/core/src/Model/WorkflowNodes.cpp
@@ -0,0 +1,309 @@
+#include "WorkflowNodes.hpp"
+
+#include "Utils/Macros.hpp"
+#include "Utils/RTTI.hpp"
+#include "Utils/Variant.hpp"
+
+#include <cassert>
+#include <variant>
+
+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;
+ }
+
+ ctx.SetConnectionValue(mOutputs[0], res);
+}
+
+bool NumericExpressionNode::IsInstance(const WorkflowNode* node) {
+ return node->GetKind() == KD_NumericExpression;
+}
+
+NumericExpressionNode::NumericExpressionNode()
+ : WorkflowNode(TransformType, KD_NumericExpression) {
+}
+
+struct FormatTextNode::Argument {
+ ArgumentType ArgumentType;
+ int PinIdx;
+
+ 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) {
+ auto& elm = *it;
+ if (auto arg = std::get_if<Argument>(&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<Element>& vec, int elmIdx) {
+ for (int i = elmIdx; i >= 0; --i) {
+ auto& elm = vec[i];
+ if (auto arg = std::get_if<Argument>(&elm)) {
+ return arg->PinIdx + 1;
+ }
+ }
+ return 0;
+ }
+};
+
+BaseValue::Kind FormatTextNode::ArgumentTypeToValueKind(FormatTextNode::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 FormatTextNode::IsInstance(const WorkflowNode* node) {
+ return node->GetKind() == KD_TextFormatting;
+}
+
+FormatTextNode::FormatTextNode()
+ : WorkflowNode(TransformType, KD_TextFormatting) {
+}
+
+int FormatTextNode::GetElementCount() const {
+ return mElements.size();
+}
+
+const FormatTextNode::Element& FormatTextNode::GetElement(int idx) const {
+ return mElements[idx];
+}
+
+void FormatTextNode::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 FormatTextNode::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 FormatTextNode::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 FormatTextNode::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 FormatTextNode::AppendElement(std::string text) {
+ mMinOutputChars += text.size();
+ mElements.push_back(std::move(text));
+}
+
+void FormatTextNode::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 FormatTextNode::RemoveElement(int idx) {
+ assert(idx >= 0 && idx < mElements.size());
+
+ PreRemoveElement(idx);
+ if (auto arg = std::get_if<Argument>(&mElements[idx])) {
+ RemoveInputPin(arg->PinIdx);
+ }
+ mElements.erase(mElements.begin() + idx);
+}
+
+void FormatTextNode::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<NumericValue>(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<TextValue>(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<DateTimeValue>(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 FormatTextNode::PreRemoveElement(int idx) {
+ auto& elm = mElements[idx];
+ if (auto arg = std::get_if<Argument>(&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
new file mode 100644
index 0000000..e7d4bfc
--- /dev/null
+++ b/core/src/Model/WorkflowNodes.hpp
@@ -0,0 +1,101 @@
+#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 FormatTextNode : 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);
+ FormatTextNode();
+
+ 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/WorkflowSteps.cpp b/core/src/Model/WorkflowSteps.cpp
deleted file mode 100644
index 815c5ca..0000000
--- a/core/src/Model/WorkflowSteps.cpp
+++ /dev/null
@@ -1,11 +0,0 @@
-#include "WorkflowSteps.hpp"
-
-NumericOperationStep::NumericOperationStep(NumericOperationStep::Type type)
- : mType{ type } {
- mInputs.resize(2);
- mInputs[0].MatchingType = BaseValue::NumericType;
- mInputs[1].MatchingType = BaseValue::NumericType;
-
- mOutputs.resize(1);
- mOutputs[0].MatchingType = BaseValue::NumericType;
-}
diff --git a/core/src/Model/WorkflowSteps.hpp b/core/src/Model/WorkflowSteps.hpp
deleted file mode 100644
index 819ca63..0000000
--- a/core/src/Model/WorkflowSteps.hpp
+++ /dev/null
@@ -1,44 +0,0 @@
-#pragma once
-
-#include "Model/Workflow.hpp"
-
-#include <memory>
-
-class NumericOperationStep : public WorkflowStep {
-public:
- enum Type {
- Addition,
- Subtraction,
- Multiplication,
- Division,
-
- InvalidType,
- TypeCount = InvalidType,
- };
-
-private:
- Type mType;
-
-public:
- NumericOperationStep(Type type);
-};
-
-class NumericExpressionStep : public WorkflowStep {
-public:
-};
-
-class FormatTextStep : public WorkflowStep {
-public:
-};
-
-class FormInputStep : public WorkflowStep {
-public:
-};
-
-class DatabaseRowsInputStep : public WorkflowStep {
-public:
-};
-
-class DocumentTemplateExpansionStep : public WorkflowStep {
-public:
-};
diff --git a/core/src/Model/fwd.hpp b/core/src/Model/fwd.hpp
index a2156aa..a7df889 100644
--- a/core/src/Model/fwd.hpp
+++ b/core/src/Model/fwd.hpp
@@ -32,14 +32,15 @@ class TransactionModel;
// Workflow.hpp
class WorkflowConnection;
-class WorkflowStep;
+class WorkflowNode;
class Workflow;
+class WorkflowEvaluationError;
class WorkflowEvaluationContext;
// WorkflowSteps.hpp
-class NumericOperationStep;
-class NumericExpressionStep;
-class FormatTextStep;
-class FormInputStep;
-class DatabaseRowsInputStep;
-class DocumentTemplateExpansionStep;
+class NumericOperationNode;
+class NumericExpressionNode;
+class FormatTextNode;
+class DocumentTemplateExpansionNode;
+class FormInputNode;
+class DatabaseRowsInputNode;