From b7d5b514e7bffd3149a99bc7f1424f8251876d85 Mon Sep 17 00:00:00 2001 From: rtk0c Date: Mon, 26 Apr 2021 14:07:28 -0700 Subject: Serialization for workflow stuff --- core/src/Model/Workflow/Evaluation.cpp | 34 ++ core/src/Model/Workflow/Evaluation.hpp | 6 + core/src/Model/Workflow/Value.cpp | 9 - core/src/Model/Workflow/Value.hpp | 17 +- core/src/Model/Workflow/Value_Main.cpp | 19 + core/src/Model/Workflow/Value_RTTI.cpp | 22 + core/src/Model/Workflow/Workflow.cpp | 562 ---------------------- core/src/Model/Workflow/Workflow.hpp | 40 +- core/src/Model/Workflow/Workflow_Main.cpp | 762 ++++++++++++++++++++++++++++++ core/src/Model/Workflow/Workflow_RTTI.cpp | 51 ++ 10 files changed, 944 insertions(+), 578 deletions(-) delete mode 100644 core/src/Model/Workflow/Value.cpp create mode 100644 core/src/Model/Workflow/Value_Main.cpp create mode 100644 core/src/Model/Workflow/Value_RTTI.cpp delete mode 100644 core/src/Model/Workflow/Workflow.cpp create mode 100644 core/src/Model/Workflow/Workflow_Main.cpp create mode 100644 core/src/Model/Workflow/Workflow_RTTI.cpp (limited to 'core/src/Model') diff --git a/core/src/Model/Workflow/Evaluation.cpp b/core/src/Model/Workflow/Evaluation.cpp index db09973..ff3edf4 100644 --- a/core/src/Model/Workflow/Evaluation.cpp +++ b/core/src/Model/Workflow/Evaluation.cpp @@ -2,6 +2,40 @@ #include +const char* WorkflowEvaluationError::FormatMessageType(enum MessageType messageType) { + switch (messageType) { + case Error: return "Error"; + case Warning: return "Warning"; + } +} + +const char* WorkflowEvaluationError::FormatPinType(enum PinType pinType) { + switch (pinType) { + case NoPin: return nullptr; + case InputPin: return "Input pin"; + case OutputPin: return "Output pin"; + } +} + +std::string WorkflowEvaluationError::Format() const { + // TODO convert to std::format + + std::string result; + result += FormatMessageType(this->Type); + result += " at "; + result += NodeId; + if (auto pinText = FormatPinType(this->PinType)) { + result += "/"; + result += pinText; + result += " "; + result += PinId; + } + result += ": "; + result += this->Message; + + return result; +} + namespace { enum class EvaluationStatus { Unevaluated, diff --git a/core/src/Model/Workflow/Evaluation.hpp b/core/src/Model/Workflow/Evaluation.hpp index be2e862..d068c45 100644 --- a/core/src/Model/Workflow/Evaluation.hpp +++ b/core/src/Model/Workflow/Evaluation.hpp @@ -26,6 +26,12 @@ public: int PinId; PinType PinType; MessageType Type; + +public: + static const char* FormatMessageType(enum MessageType messageType); + static const char* FormatPinType(enum PinType pinType); + + std::string Format() const; }; class WorkflowEvaluationContext { diff --git a/core/src/Model/Workflow/Value.cpp b/core/src/Model/Workflow/Value.cpp deleted file mode 100644 index 7e5aabf..0000000 --- a/core/src/Model/Workflow/Value.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#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 index eb99c14..2748e16 100644 --- a/core/src/Model/Workflow/Value.hpp +++ b/core/src/Model/Workflow/Value.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + class BaseValue { public: enum Kind { @@ -8,14 +11,17 @@ public: KD_DateTime, /// An unspecified type, otherwise known as "any" in some contexts. - KindInvalid, - KindCount = KindInvalid, + InvalidKind, + KindCount = InvalidKind, }; private: Kind mKind; public: + static const char* FormatKind(Kind kind); + static std::unique_ptr CreateByKind(Kind kind); + BaseValue(Kind kind); virtual ~BaseValue() = default; @@ -25,4 +31,11 @@ public: BaseValue& operator=(BaseValue&&) = default; Kind GetKind() const; + + // TODO get constant editor + + /// The functions \c ReadFrom, \c WriteTo will only be valid to call if this function returns true. + virtual bool SupportsConstant() const; + virtual void ReadFrom(std::istream& stream); + virtual void WriteTo(std::ostream& stream); }; diff --git a/core/src/Model/Workflow/Value_Main.cpp b/core/src/Model/Workflow/Value_Main.cpp new file mode 100644 index 0000000..2e64987 --- /dev/null +++ b/core/src/Model/Workflow/Value_Main.cpp @@ -0,0 +1,19 @@ +#include "Value.hpp" + +BaseValue::BaseValue(Kind kind) + : mKind{ kind } { +} + +BaseValue::Kind BaseValue::GetKind() const { + return mKind; +} + +bool BaseValue::SupportsConstant() const { + return false; +} + +void BaseValue::ReadFrom(std::istream& stream) { +} + +void BaseValue::WriteTo(std::ostream& stream) { +} diff --git a/core/src/Model/Workflow/Value_RTTI.cpp b/core/src/Model/Workflow/Value_RTTI.cpp new file mode 100644 index 0000000..f2c8f92 --- /dev/null +++ b/core/src/Model/Workflow/Value_RTTI.cpp @@ -0,0 +1,22 @@ +#include "Value.hpp" + +#include "Model/Workflow/Values/BasicValues.hpp" +#include "Utils/Macros.hpp" + +const char* BaseValue::FormatKind(Kind kind) { + switch (kind) { + case KD_Numeric: return "Numeric"; + case KD_Text: return "Text"; + case KD_DateTime: return "Date/time"; + case InvalidKind: UNREACHABLE; + } +} + +std::unique_ptr BaseValue::CreateByKind(BaseValue::Kind kind) { + switch (kind) { + case KD_Numeric: return std::make_unique(); + case KD_Text: return std::make_unique(); + case KD_DateTime: return std::make_unique(); + case InvalidKind: return nullptr; + } +} diff --git a/core/src/Model/Workflow/Workflow.cpp b/core/src/Model/Workflow/Workflow.cpp deleted file mode 100644 index a1af44a..0000000 --- a/core/src/Model/Workflow/Workflow.cpp +++ /dev/null @@ -1,562 +0,0 @@ -#include "Workflow.hpp" - -#include -#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 } - , mDepth{ -1 } { -} - -Vec2i WorkflowNode::GetPosition() const { - return mPosition; -} - -void WorkflowNode::SetPosition(const Vec2i& position) { - mPosition = position; -} - -size_t WorkflowNode::GetId() const { - return mId; -} - -WorkflowNode::Type WorkflowNode::GetType() const { - return mType; -} - -WorkflowNode::Kind WorkflowNode::GetKind() const { - return mKind; -} - -int WorkflowNode::GetDepth() const { - return mDepth; -} - -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; -} - -void Workflow::RemoveConnection(size_t id) { - auto& conn = mConnections[id]; - if (!conn.IsValid()) return; - - for (auto& point : conn.GetSourcePoints()) { - auto& node = *mNodes[point.Node]; - auto& pin = node.mOutputs[point.Pin]; - pin.Connection = WorkflowNode::kInvalidId; - } - for (auto& point : conn.GetDestinationPoints()) { - auto& node = *mNodes[point.Node]; - auto& pin = node.mInputs[point.Pin]; - pin.Connection = WorkflowNode::kInvalidId; - } - - conn = {}; - mDepthsDirty = true; -} - -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; - - mDepthsDirty = true; - return true; -} - -// TODO cleanup these two implementation - -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 = WorkflowNode::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 = WorkflowNode::kInvalidId; - } - sp.Connection = WorkflowNode::kInvalidId; - - conn = {}; - } break; - } - - mDepthsDirty = true; - 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 = WorkflowNode::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 = WorkflowNode::kInvalidId; - } - dp.Connection = WorkflowNode::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 = WorkflowNode::kInvalidId; - } break; - } - - mDepthsDirty = true; - return true; -} - -const std::vector>& Workflow::GetDepthGroups() const { - return mDepthGroups; -} - -bool Workflow::DoesDepthNeedsUpdate() const { - return mDepthsDirty; -} - -Workflow::GraphUpdateResult Workflow::UpdateGraph(bool getInfo) { - if (!mDepthsDirty) { - return GraphUpdate_NoWorkToDo{}; - } - - // Terminology: - // - Dependency = nodes its input pins are connected to - // - Dependents = nodes its output pins are connected to - - struct WorkingNode { - // The max depth out of all dependency nodes, maintained during the traversal and committed as the actual depth - // when all dependencies of this node has been resolved. Add 1 to get the depth that will be assigned to the node. - int MaximumDepth = 0; - int FulfilledInputCount = 0; - }; - - std::vector workingNodes; - std::queue q; - - // Check if all dependencies of this node is satisfied - auto CheckNodeDependencies = [&](WorkflowNode& node) -> bool { - for (auto& pin : node.mInputs) { - if (!pin.IsConnected()) { - return false; - } - } - return true; - }; - - workingNodes.reserve(mNodes.size()); - { - std::vector unsatisfiedNodes; - for (size_t i = 0; i < mNodes.size(); ++i) { - auto& node = mNodes[i]; - workingNodes.push_back(WorkingNode{}); - - if (!node) continue; - - if (!CheckNodeDependencies(*node)) { - if (getInfo) unsatisfiedNodes.push_back(i); - } - - node->mDepth = -1; - - // Start traversing with the input nodes - if (node->GetType() == WorkflowNode::InputType) { - q.push(i); - } - } - - if (!unsatisfiedNodes.empty()) { - return GraphUpdate_UnsatisfiedDependencies{ std::move(unsatisfiedNodes) }; - } - } - - auto HasCyclicReference = [&](WorkflowNode& node) -> bool { - // TODO - return false; - }; - - auto ProcessNode = [&](WorkflowNode& node) -> void { - for (auto& pin : node.mOutputs) { - if (!pin.IsConnected()) continue; - auto& conn = mConnections[pin.Connection]; - - for (auto& point : conn.GetDestinationPoints()) { - auto& wn = workingNodes[point.Node]; - auto& n = *mNodes[point.Node].get(); - - if (HasCyclicReference(n)) { - // TODO - break; - } - - wn.FulfilledInputCount++; - wn.MaximumDepth = std::max(node.mDepth, wn.MaximumDepth); - - // Node's dependency is fulfilled, we can process its dependents next - // We use >= here because for a many-to-one pin, the dependency is an "or" relation ship, i.e. any of the nodes firing before this will fulfill the requirement - if (n.mInputs.size() >= wn.FulfilledInputCount) { - n.mDepth = wn.MaximumDepth + 1; - } - } - } - }; - - int processedNodes = 0; - while (!q.empty()) { - auto& wn = workingNodes[q.front()]; - auto& n = *mNodes[q.front()]; - q.pop(); - processedNodes++; - - ProcessNode(n); - } - - if (processedNodes < mNodes.size()) { - // There is unreachable nodes - if (!getInfo) { - return GraphUpdate_UnreachableNodes{}; - } - - std::vector unreachableNodes; - for (size_t i = 0; i < mNodes.size(); ++i) { - auto& wn = workingNodes[i]; - auto& n = *mNodes[i]; - - // This is a reachable node - if (n.mDepth != -1) continue; - - unreachableNodes.push_back(i); - } - - return GraphUpdate_UnreachableNodes{ std::move(unreachableNodes) }; - } - - return GraphUpdate_Success{}; -} - -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 index 9c02168..db341af 100644 --- a/core/src/Model/Workflow/Workflow.hpp +++ b/core/src/Model/Workflow/Workflow.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -41,6 +42,10 @@ public: std::span GetSourcePoints() const; std::span GetDestinationPoints(); std::span GetDestinationPoints() const; + + void DrawDebugInfo() const; + void ReadFrom(std::istream& stream); + void WriteTo(std::ostream& stream); }; class WorkflowNode { @@ -71,7 +76,7 @@ public: protected: struct InputPin { size_t Connection = WorkflowConnection::kInvalidId; - BaseValue::Kind MatchingType = BaseValue::KindInvalid; + BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool ConnectionToConst = false; bool AllowsMultipleConnections = false; @@ -84,7 +89,7 @@ protected: struct OutputPin { size_t Connection = WorkflowConnection::kInvalidId; - BaseValue::Kind MatchingType = BaseValue::KindInvalid; + BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool AllowsMultipleConnections = false; bool IsConnected() const; @@ -100,11 +105,14 @@ protected: std::vector mInputs; std::vector mOutputs; Vec2i mPosition; - Type mType; Kind mKind; int mDepth; public: + static const char* FormatKind(Kind kind); + static const char* FormatType(Type type); + static std::unique_ptr CreateByKind(Kind kind); + WorkflowNode(Type type, Kind kind); virtual ~WorkflowNode() = default; @@ -113,14 +121,17 @@ public: WorkflowNode(WorkflowNode&&) = default; WorkflowNode& operator=(WorkflowNode&&) = default; - Vec2i GetPosition() const; void SetPosition(const Vec2i& position); + Vec2i GetPosition() const; size_t GetId() const; - Type GetType() const; Kind GetKind() const; int GetDepth() const; + Type GetType() const; + bool IsInputNode() const; + bool IsOutputNode() const; + void ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId); void DisconnectInput(int nodeId); bool IsInputConnected(int nodeId) const; @@ -131,6 +142,15 @@ public: virtual void Evaluate(WorkflowEvaluationContext& ctx) = 0; + void Draw(); + virtual void DrawExtra() {} + + void DrawDebugInfo() const; + virtual void DrawExtraDebugInfo() const {} + + virtual void ReadFrom(std::istream& istream); + virtual void WriteTo(std::ostream& ostream); + protected: InputPin& InsertInputPin(int atIdx); void RemoveInputPin(int pin); @@ -153,6 +173,9 @@ private: std::vector> mNodes; std::vector> mConstants; std::vector> mDepthGroups; + int mConnectionCount; + int mNodeCount; + int mConstantCount; bool mDepthsDirty = true; public: @@ -190,6 +213,13 @@ public: /// When `getInfo == false, the corresponding error code is returned but without/with empty payloads. GraphUpdateResult UpdateGraph(bool getInfo = true); + enum ReadResult { + ReadSuccess, + ReadInvalidVersion, + }; + ReadResult ReadFrom(std::istream& stream); + void WriteTo(std::ostream& stream); + private: std::pair AllocWorkflowConnection(); std::pair&, size_t> AllocWorkflowStep(); diff --git a/core/src/Model/Workflow/Workflow_Main.cpp b/core/src/Model/Workflow/Workflow_Main.cpp new file mode 100644 index 0000000..c8657e8 --- /dev/null +++ b/core/src/Model/Workflow/Workflow_Main.cpp @@ -0,0 +1,762 @@ +#include "Workflow.hpp" + +#include +#include +#include +#include +#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; + } +} + +static void DrawConnectionPoints(const std::vector& points, const char* pinHint) { + ImGui::Indent(32.0f); + for (auto& pt : points) { + ImGui::Text("{ Node = %llu, Pin (%s) = %d }", pt.Node, pinHint, pt.Pin); + if (ImGui::IsItemHovered()) { + // TODO highlight node + } + } + ImGui::Unindent(); +} + +static void DrawConnectionPoint(const WorkflowConnection::ConnectionPoint& point, const char* pinHint) { + ImGui::Indent(32.0f); + ImGui::Text("{ Node = %llu, Pin (%s) = %d }", point.Node, pinHint, point.Pin); + ImGui::Unindent(); +} + +void WorkflowConnection::DrawDebugInfo() const { + ImGui::BeginTooltip(); + switch (ConnectionDirection) { + case ManyToOne: { + ImGui::Text("Type: many-to-one"); + ImGui::Text("Sources:"); + ::DrawConnectionPoints(MultiConnections, "output"); + ImGui::Text("Destination:"); + ::DrawConnectionPoint(SingleConnection, "input"); + } break; + + case OneToMany: { + ImGui::Text("Type: one-to-many"); + ImGui::Text("Source:"); + ::DrawConnectionPoint(SingleConnection, "output"); + ImGui::Text("Destinations:"); + ::DrawConnectionPoints(MultiConnections, "input"); + } break; + } + ImGui::EndTooltip(); +} + +static WorkflowConnection::ConnectionPoint ReadConnectionPoint(std::istream& stream) { + WorkflowConnection::ConnectionPoint pt; + stream >> pt.Node; + stream >> pt.Pin; + return pt; +} + +void WorkflowConnection::ReadFrom(std::istream& stream) { + int n; + stream >> n; + ConnectionDirection = (Direction)n; + + SingleConnection = ::ReadConnectionPoint(stream); + + size_t size; + stream >> size; + for (size_t i = 0; i < size; ++i) { + MultiConnections.push_back(::ReadConnectionPoint(stream)); + } +} + +static void WriteConnectionPoint(std::ostream& stream, const WorkflowConnection::ConnectionPoint& pt) { + stream << pt.Node; + stream << pt.Pin; +} + +void WorkflowConnection::WriteTo(std::ostream& stream) { + stream << (int)ConnectionDirection; + ::WriteConnectionPoint(stream, SingleConnection); + stream << (size_t)MultiConnections.size(); + for (auto& pt : MultiConnections) { + ::WriteConnectionPoint(stream, pt); + } +} + +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 } + , mDepth{ -1 } { +} + +Vec2i WorkflowNode::GetPosition() const { + return mPosition; +} + +void WorkflowNode::SetPosition(const Vec2i& position) { + mPosition = position; +} + +size_t WorkflowNode::GetId() const { + return mId; +} + +WorkflowNode::Type WorkflowNode::GetType() const { + return mType; +} + +int WorkflowNode::GetDepth() const { + return mDepth; +} + +WorkflowNode::Kind WorkflowNode::GetKind() const { + if (IsInputNode()) { + return InputType; + } else if (IsOutputNode()) { + return OutputType; + } else { + return TransformType; + } +} + +bool WorkflowNode::IsInputNode() const { + return mInputs.size() == 0; +} + +bool WorkflowNode::IsOutputNode() const { + return mOutputs.size() == 0; +} + +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(); +} + +void WorkflowNode::Draw() { +} + +void WorkflowNode::DrawDebugInfo() const { + ImGui::BeginTooltip(); + ImGui::Text("Node kind: %s", FormatKind(mKind)); + ImGui::Text("Node type: %s", FormatType(mType)); + ImGui::Text("Node ID: %llu", mId); + ImGui::Text("Depth: %d", mDepth); + DrawExtraDebugInfo(); + ImGui::EndTooltip(); +} + +void WorkflowNode::ReadFrom(std::istream& stream) { + stream >> mId; + stream >> mPosition.x >> mPosition.y; +} + +void WorkflowNode::WriteTo(std::ostream& stream) { + stream << mId; + stream << mPosition.x << mPosition.y; +} + +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; +} + +void Workflow::RemoveConnection(size_t id) { + auto& conn = mConnections[id]; + if (!conn.IsValid()) return; + + for (auto& point : conn.GetSourcePoints()) { + auto& node = *mNodes[point.Node]; + auto& pin = node.mOutputs[point.Pin]; + pin.Connection = WorkflowNode::kInvalidId; + } + for (auto& point : conn.GetDestinationPoints()) { + auto& node = *mNodes[point.Node]; + auto& pin = node.mInputs[point.Pin]; + pin.Connection = WorkflowNode::kInvalidId; + } + + conn = {}; + mDepthsDirty = true; +} + +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; + + mDepthsDirty = true; + return true; +} + +// TODO cleanup these two implementation + +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 = WorkflowNode::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 = WorkflowNode::kInvalidId; + } + sp.Connection = WorkflowNode::kInvalidId; + + conn = {}; + } break; + } + + mDepthsDirty = true; + 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 = WorkflowNode::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 = WorkflowNode::kInvalidId; + } + dp.Connection = WorkflowNode::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 = WorkflowNode::kInvalidId; + } break; + } + + mDepthsDirty = true; + return true; +} + +const std::vector>& Workflow::GetDepthGroups() const { + return mDepthGroups; +} + +bool Workflow::DoesDepthNeedsUpdate() const { + return mDepthsDirty; +} + +Workflow::GraphUpdateResult Workflow::UpdateGraph(bool getInfo) { + if (!mDepthsDirty) { + return GraphUpdate_NoWorkToDo{}; + } + + // Terminology: + // - Dependency = nodes its input pins are connected to + // - Dependents = nodes its output pins are connected to + + struct WorkingNode { + // The max depth out of all dependency nodes, maintained during the traversal and committed as the actual depth + // when all dependencies of this node has been resolved. Add 1 to get the depth that will be assigned to the node. + int MaximumDepth = 0; + int FulfilledInputCount = 0; + }; + + std::vector workingNodes; + std::queue q; + + // Check if all dependencies of this node is satisfied + auto CheckNodeDependencies = [&](WorkflowNode& node) -> bool { + for (auto& pin : node.mInputs) { + if (!pin.IsConnected()) { + return false; + } + } + return true; + }; + + workingNodes.reserve(mNodes.size()); + { + std::vector unsatisfiedNodes; + for (size_t i = 0; i < mNodes.size(); ++i) { + auto& node = mNodes[i]; + workingNodes.push_back(WorkingNode{}); + + if (!node) continue; + + if (!CheckNodeDependencies(*node)) { + if (getInfo) unsatisfiedNodes.push_back(i); + } + + node->mDepth = -1; + + // Start traversing with the input nodes + if (node->GetType() == WorkflowNode::InputType) { + q.push(i); + } + } + + if (!unsatisfiedNodes.empty()) { + return GraphUpdate_UnsatisfiedDependencies{ std::move(unsatisfiedNodes) }; + } + } + + auto HasCyclicReference = [&](WorkflowNode& node) -> bool { + // TODO + return false; + }; + + auto ProcessNode = [&](WorkflowNode& node) -> void { + for (auto& pin : node.mOutputs) { + if (!pin.IsConnected()) continue; + auto& conn = mConnections[pin.Connection]; + + for (auto& point : conn.GetDestinationPoints()) { + auto& wn = workingNodes[point.Node]; + auto& n = *mNodes[point.Node].get(); + + if (HasCyclicReference(n)) { + // TODO + break; + } + + wn.FulfilledInputCount++; + wn.MaximumDepth = std::max(node.mDepth, wn.MaximumDepth); + + // Node's dependency is fulfilled, we can process its dependents next + // We use >= here because for a many-to-one pin, the dependency is an "or" relation ship, i.e. any of the nodes firing before this will fulfill the requirement + if (n.mInputs.size() >= wn.FulfilledInputCount) { + n.mDepth = wn.MaximumDepth + 1; + } + } + } + }; + + int processedNodes = 0; + while (!q.empty()) { + auto& wn = workingNodes[q.front()]; + auto& n = *mNodes[q.front()]; + q.pop(); + processedNodes++; + + ProcessNode(n); + } + + if (processedNodes < mNodes.size()) { + // There is unreachable nodes + if (!getInfo) { + return GraphUpdate_UnreachableNodes{}; + } + + std::vector unreachableNodes; + for (size_t i = 0; i < mNodes.size(); ++i) { + auto& wn = workingNodes[i]; + auto& n = *mNodes[i]; + + // This is a reachable node + if (n.mDepth != -1) continue; + + unreachableNodes.push_back(i); + } + + return GraphUpdate_UnreachableNodes{ std::move(unreachableNodes) }; + } + + return GraphUpdate_Success{}; +} + +Workflow::ReadResult Workflow::ReadFrom(std::istream& stream) { + auto DeserializeV0 = [&]() { + size_t connectionsStorage, nodesStorage, constantsStorage; + stream >> connectionsStorage >> nodesStorage >> constantsStorage; + + uint32_t connectionCount, nodeCount, constantCount; + stream >> connectionCount >> nodeCount >> constantCount; + + for (uint32_t i = 0; i < connectionCount; ++i) { + size_t idx; + stream >> idx; + + mConnections[idx].ReadFrom(stream); + } + + for (uint32_t i = 0; i < nodeCount; ++i) { + size_t idx; + stream >> idx; + + uint32_t nKind; + stream >> nKind; + auto kind = (WorkflowNode::Kind)nKind; + + mNodes[idx] = WorkflowNode::CreateByKind(kind); + mNodes[idx]->ReadFrom(stream); + } + + for (uint32_t i = 0; i < constantCount; ++i) { + size_t idx; + stream >> idx; + + uint32_t nKind; + stream >> nKind; + autp kind = (BaseValue::Kind)nKind; + + mConstants[idx] = BaseValue::CreateByKind(kind); + mConstants[idx]->ReadFrom(stream); + } + }; + + uint64_t version; + stream >> version; + + switch (version) { + case 0: DeserializeV0(); break; + default: return ReadInvalidVersion; + } + return ReadSuccess; +} + +void Workflow::WriteTo(std::ostream& stream) { + // Version + stream << (uint64_t)0; + + stream << (size_t)mConnections.size(); + stream << (size_t)mNodes.size(); + stream << (size_t)mConstants.size(); + stream << (uint32_t)mConnectionCount; + stream << (uint32_t)mNodeCount; + stream << (uint32_t)mConstantCount; + + for (size_t i = 0; i < mConnections.size(); ++i) { + auto& conn = mConnections[i]; + if (conn.IsValid()) { + stream << i; + conn.WriteTo(stream); + } + } + + for (size_t i = 0; i < mNodes.size(); ++i) { + auto& node = mNodes[i]; + if (node) { + stream << i << (uint32_t)node->GetKind(); + node->WriteTo(stream); + } + } + + for (size_t i = 0; i < mConstants.size(); ++i) { + auto& constant = mConstants[i]; + if (constant) { + stream << i; + constant->WriteTo(stream); + } + } +} + +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_RTTI.cpp b/core/src/Model/Workflow/Workflow_RTTI.cpp new file mode 100644 index 0000000..0719372 --- /dev/null +++ b/core/src/Model/Workflow/Workflow_RTTI.cpp @@ -0,0 +1,51 @@ +#include "Workflow.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 "Utils/Macros.hpp" + +#include + +const char* WorkflowNode::FormatKind(Kind kind) { + switch (kind) { + case KD_NumericAddition: return "NumericOperation (addition)"; + case KD_NumericSubtraction: return "NumericOperation (subtraction)"; + case KD_NumericMultiplication: return "NumericOperation (multiplication)"; + case KD_NumericDivision: return "NumericOperation (division)"; + case KD_NumericExpression: return "NumericExpression"; + case KD_TextFormatting: return "TextFormatting"; + case KD_DocumentTemplateExpansion: return "DocumentTemplateExpansion"; + case KD_FormInput: return "FormInput"; + case KD_DatabaseRowsInput: return "DatabaseRowsInput"; + + case InvalidKind: UNREACHABLE; + } +} + +const char* WorkflowNode::FormatType(Type type) { + switch (type) { + case InputType: return "input"; + case TransformType: return "transform"; + case OutputType: return "output"; + default: UNREACHABLE; + } +} + +std::unique_ptr WorkflowNode::CreateByKind(WorkflowNode::Kind kind) { + switch (kind) { + case KD_NumericAddition: return std::make_unique(NumericOperationNode::Addition); + case KD_NumericSubtraction: return std::make_unique(NumericOperationNode::Subtraction); + case KD_NumericMultiplication: return std::make_unique(NumericOperationNode::Multiplication); + case KD_NumericDivision: return std::make_unique(NumericOperationNode::Division); + + case KD_NumericExpression: return std::make_unique(); + case KD_TextFormatting: return std::make_unique(); + case KD_DocumentTemplateExpansion: return std::make_unique(); + case KD_FormInput: return std::make_unique(); + case KD_DatabaseRowsInput: return std::make_unique(); + + case InvalidKind: return nullptr; + } +} -- cgit v1.2.3-70-g09d2