aboutsummaryrefslogtreecommitdiff
path: root/core/src/Model/Workflow/Workflow.cpp
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2021-04-19 14:00:47 -0700
committerrtk0c <[email protected]>2021-04-19 14:00:47 -0700
commit1e09caaa2980fe901453b4b90985967a51157887 (patch)
treedf61974f9a5efa9a6732bd6d7b1ec1e6d1af182a /core/src/Model/Workflow/Workflow.cpp
parentb00b306de1140cb7b759ed0f639e8210fd7dffa6 (diff)
Split workflow into multiple files, fix unity build
Diffstat (limited to 'core/src/Model/Workflow/Workflow.cpp')
-rw-r--r--core/src/Model/Workflow/Workflow.cpp396
1 files changed, 396 insertions, 0 deletions
diff --git a/core/src/Model/Workflow/Workflow.cpp b/core/src/Model/Workflow/Workflow.cpp
new file mode 100644
index 0000000..a32149e
--- /dev/null
+++ b/core/src/Model/Workflow/Workflow.cpp
@@ -0,0 +1,396 @@
+#include "Workflow.hpp"
+
+#include <algorithm>
+#include <cassert>
+#include <queue>
+#include <utility>
+
+WorkflowConnection::WorkflowConnection()
+ : MultiConnections{}
+ , SingleConnection{ WorkflowNode::kInvalidId, -1 }
+ , ConnectionDirection{ OneToMany } {
+}
+
+bool WorkflowConnection::IsValid() const {
+ return SingleConnection.Node != WorkflowNode::kInvalidId;
+}
+
+std::span<WorkflowConnection::ConnectionPoint> WorkflowConnection::GetSourcePoints() {
+ switch (ConnectionDirection) {
+ case ManyToOne: return MultiConnections;
+ case OneToMany: return { &SingleConnection, 1 };
+ }
+}
+
+std::span<const WorkflowConnection::ConnectionPoint> WorkflowConnection::GetSourcePoints() const {
+ switch (ConnectionDirection) {
+ case ManyToOne: return MultiConnections;
+ case OneToMany: return { &SingleConnection, 1 };
+ }
+}
+
+std::span<WorkflowConnection::ConnectionPoint> WorkflowConnection::GetDestinationPoints() {
+ switch (ConnectionDirection) {
+ case ManyToOne: return { &SingleConnection, 1 };
+ case OneToMany: return MultiConnections;
+ }
+}
+
+std::span<const WorkflowConnection::ConnectionPoint> WorkflowConnection::GetDestinationPoints() const {
+ switch (ConnectionDirection) {
+ case ManyToOne: return { &SingleConnection, 1 };
+ case OneToMany: return MultiConnections;
+ }
+}
+
+bool WorkflowNode::InputPin::IsConstantConnection() const {
+ return ConnectionToConst && IsConnected();
+}
+
+bool WorkflowNode::InputPin::IsConnected() const {
+ return Connection != WorkflowConnection::kInvalidId;
+}
+
+BaseValue::Kind WorkflowNode::InputPin::GetMatchingType() const {
+ return MatchingType;
+}
+
+WorkflowConnection::Direction WorkflowNode::InputPin::GetSupportedDirection() const {
+ return AllowsMultipleConnections ? WorkflowConnection::ManyToOne : WorkflowConnection::OneToMany;
+}
+
+bool WorkflowNode::OutputPin::IsConnected() const {
+ return Connection != WorkflowConnection::kInvalidId;
+}
+
+BaseValue::Kind WorkflowNode::OutputPin::GetMatchingType() const {
+ return MatchingType;
+}
+
+WorkflowConnection::Direction WorkflowNode::OutputPin::GetSupportedDirection() const {
+ return AllowsMultipleConnections ? WorkflowConnection::OneToMany : WorkflowConnection::ManyToOne;
+}
+
+WorkflowNode::WorkflowNode(Type type, Kind kind)
+ : mType{ type }
+ , mKind{ kind } {
+}
+
+size_t WorkflowNode::GetId() const {
+ return mId;
+}
+
+WorkflowNode::Type WorkflowNode::GetType() const {
+ return mType;
+}
+
+WorkflowNode::Kind WorkflowNode::GetKind() const {
+ return mKind;
+}
+
+void WorkflowNode::ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId) {
+ mWorkflow->Connect(*this, nodeId, output, outputNodeId);
+}
+
+void WorkflowNode::DisconnectInput(int nodeId) {
+ mWorkflow->DisconnectByDestination(*this, nodeId);
+}
+
+bool WorkflowNode::IsInputConnected(int nodeId) const {
+ return mInputs[nodeId].IsConnected();
+}
+
+void WorkflowNode::ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId) {
+ mWorkflow->Connect(input, inputNodeId, *this, nodeId);
+}
+
+void WorkflowNode::DisconnectOutput(int nodeId) {
+ mWorkflow->DisconnectBySource(*this, nodeId);
+}
+
+bool WorkflowNode::IsOutputConnected(int nodeId) const {
+ return mOutputs[nodeId].IsConnected();
+}
+
+WorkflowNode::InputPin& WorkflowNode::InsertInputPin(int atIdx) {
+ assert(atIdx >= 0 && atIdx < mInputs.size());
+
+ mInputs.push_back(InputPin{});
+ for (int i = (int)mInputs.size() - 1, end = atIdx + 1; i >= end; --i) {
+ SwapInputPin(i, i + 1);
+ }
+
+ return mInputs[atIdx];
+}
+
+void WorkflowNode::RemoveInputPin(int pin) {
+ DisconnectInput(pin);
+ for (int i = 0, end = (int)mInputs.size() - 1; i < end; ++i) {
+ SwapInputPin(i, i + 1);
+ }
+ mInputs.resize(mInputs.size() - 1);
+}
+
+void WorkflowNode::SwapInputPin(int a, int b) {
+ auto& pinA = mInputs[a];
+ auto& pinB = mInputs[b];
+
+ if (mWorkflow) {
+ // Delay assignment to ConnectionPoint::Pin so that the pinB loop (if it happens to run and looks in the same Connection) doesn't match the point updated in the pinA loop
+ using Pt = WorkflowConnection::ConnectionPoint;
+ Pt* pointA = nullptr;
+ Pt* pointB = nullptr;
+
+ if (pinA.IsConnected() && !pinA.IsConstantConnection()) {
+ auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetDestinationPoints();
+ auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a });
+ pointA = it == pts.end() ? nullptr : &*it;
+ }
+ if (pinB.IsConnected() && !pinB.IsConstantConnection()) {
+ auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetDestinationPoints();
+ auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b });
+ pointB = it == pts.end() ? nullptr : &*it;
+ }
+
+ if (pointA) pointA->Pin = b;
+ if (pointB) pointB->Pin = a;
+ }
+
+ std::swap(pinA, pinB);
+}
+
+WorkflowNode::OutputPin& WorkflowNode::InsertOutputPin(int atIdx) {
+ assert(atIdx >= 0 && atIdx < mOutputs.size());
+
+ mOutputs.push_back(OutputPin{});
+ for (int i = (int)mOutputs.size() - 1, end = atIdx + 1; i >= end; --i) {
+ SwapOutputPin(i, i + 1);
+ }
+
+ return mOutputs[atIdx];
+}
+
+void WorkflowNode::RemoveOutputPin(int pin) {
+ DisconnectOutput(pin);
+ for (int i = 0, end = (int)mOutputs.size() - 1; i < end; ++i) {
+ SwapInputPin(i, i + 1);
+ }
+ mOutputs.resize(mOutputs.size() - 1);
+}
+
+void WorkflowNode::SwapOutputPin(int a, int b) {
+ auto& pinA = mOutputs[a];
+ auto& pinB = mOutputs[b];
+
+ if (mWorkflow) {
+ using Pt = WorkflowConnection::ConnectionPoint;
+ Pt* pointA = nullptr;
+ Pt* pointB = nullptr;
+
+ if (pinA.IsConnected()) {
+ auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetSourcePoints();
+ auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a });
+ pointA = it == pts.end() ? nullptr : &*it;
+ }
+ if (pinB.IsConnected()) {
+ auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetSourcePoints();
+ auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b });
+ pointB = it == pts.end() ? nullptr : &*it;
+ }
+
+ if (pointA) pointA->Pin = b;
+ if (pointB) pointB->Pin = a;
+ }
+
+ std::swap(pinA, pinB);
+}
+
+WorkflowConnection* Workflow::GetConnectionById(size_t id) {
+ return &mConnections[id];
+}
+
+WorkflowNode* Workflow::GetStepById(size_t id) {
+ return mNodes[id].get();
+}
+
+BaseValue* Workflow::GetConstantById(size_t id) {
+ return mConstants[id].get();
+}
+
+void Workflow::AddStep(std::unique_ptr<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;
+}
+
+bool Workflow::Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin) {
+ auto& src = sourceNode.mOutputs[sourcePin];
+ auto& dst = destinationNode.mInputs[destinationPin];
+
+ // Equivalent to `if (o.GetSupportedDirection() != i.GetSupportedDirection()) return false;`
+ // Cannot connect two pins of different type
+ if (src.AllowsMultipleConnections == dst.AllowsMultipleConnections) return false;
+ // Would be same as `dst.GetSupportedDirection()` because we validated that they are the same above
+ auto direction = src.GetSupportedDirection();
+
+ // TODO report error to user?
+ if (src.GetMatchingType() != dst.GetMatchingType()) {
+ return false;
+ }
+
+ using Pt = WorkflowConnection::ConnectionPoint;
+ Pt* srcConnPt;
+ Pt* dstConnPt;
+ size_t connId;
+
+ switch (direction) {
+ case WorkflowConnection::ManyToOne: {
+ if (dst.IsConnected()) return false;
+
+ WorkflowConnection* conn;
+ if (src.IsConnected()) {
+ conn = &mConnections[src.Connection];
+ connId = src.Connection;
+ } else {
+ auto p = AllocWorkflowConnection();
+ conn = &p.first;
+ connId = p.second;
+ }
+
+ srcConnPt = &conn->MultiConnections.emplace_back();
+ dstConnPt = &conn->SingleConnection;
+ } break;
+
+ case WorkflowConnection::OneToMany: {
+ if (src.IsConnected()) return false;
+
+ WorkflowConnection* conn;
+ if (dst.IsConnected()) {
+ conn = &mConnections[src.Connection];
+ connId = src.Connection;
+ } else {
+ auto p = AllocWorkflowConnection();
+ conn = &p.first;
+ connId = p.second;
+ }
+
+ srcConnPt = &conn->SingleConnection;
+ dstConnPt = &conn->MultiConnections.emplace_back();
+ } break;
+ }
+
+ srcConnPt->Node = sourceNode.GetId();
+ srcConnPt->Pin = sourcePin;
+ dstConnPt->Node = destinationNode.GetId();
+ dstConnPt->Pin = destinationPin;
+
+ src.Connection = connId;
+ dst.Connection = connId;
+ return true;
+}
+
+bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, int sourcePin) {
+ auto& sp = sourceNode.mOutputs[sourcePin];
+ if (!sp.IsConnected()) return false;
+
+ auto& conn = mConnections[sp.Connection];
+
+ using Pt = WorkflowConnection::ConnectionPoint;
+ switch (sp.GetSupportedDirection()) {
+ case WorkflowConnection::ManyToOne: {
+ // Recessive pin, remove ConnectionPoint associated with this pin only
+
+ auto& vec = conn.MultiConnections;
+ vec.erase(std::remove(vec.begin(), vec.end(), Pt{ sourceNode.GetId(), sourcePin }), vec.end());
+
+ sp.Connection = WorkflowConnection::kInvalidId;
+ } break;
+
+ case WorkflowConnection::OneToMany: {
+ // Dominate pin, removes whole connection
+
+ for (auto& pt : conn.MultiConnections) {
+ auto& node = *mNodes[pt.Node];
+ node.mInputs[pt.Pin].Connection = WorkflowConnection::kInvalidId;
+ }
+ sp.Connection = WorkflowConnection::kInvalidId;
+
+ conn = {};
+ } break;
+ }
+
+ return true;
+}
+
+bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin) {
+ auto& dp = destinationNode.mInputs[destinationPin];
+ if (!dp.IsConnected()) return false;
+ if (dp.IsConstantConnection()) {
+ dp.ConnectionToConst = false;
+ dp.Connection = WorkflowConnection::kInvalidId;
+ return true;
+ }
+
+ auto& conn = mConnections[dp.Connection];
+
+ using Pt = WorkflowConnection::ConnectionPoint;
+ switch (dp.GetSupportedDirection()) {
+ case WorkflowConnection::ManyToOne: {
+ // Dominate pin, removes whole connection
+
+ for (auto& pt : conn.MultiConnections) {
+ auto& node = *mNodes[pt.Node];
+ node.mOutputs[pt.Pin].Connection = WorkflowConnection::kInvalidId;
+ }
+ dp.Connection = WorkflowConnection::kInvalidId;
+
+ conn = {};
+ } break;
+
+ case WorkflowConnection::OneToMany: {
+ // Recessive pin, remove ConnectionPoint associated with this pin only
+
+ auto& vec = conn.MultiConnections;
+ vec.erase(std::remove(vec.begin(), vec.end(), Pt{ destinationNode.GetId(), destinationPin }), vec.end());
+
+ dp.Connection = WorkflowConnection::kInvalidId;
+ } break;
+ }
+
+ return true;
+}
+
+std::pair<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 };
+}