#include "Workflow.hpp" #include #include #include WorkflowConnection::WorkflowConnection() : MultiConnections{} , SingleConnection{ WorkflowNode::kInvalidId, -1 } , ConnectionDirection{ OneToMany } { } bool WorkflowConnection::IsValid() const { return SingleConnection.Node != WorkflowNode::kInvalidId; } std::span WorkflowConnection::GetSourcePoints() { switch (ConnectionDirection) { case ManyToOne: return MultiConnections; case OneToMany: return { &SingleConnection, 1 }; } } std::span WorkflowConnection::GetSourcePoints() const { switch (ConnectionDirection) { case ManyToOne: return MultiConnections; case OneToMany: return { &SingleConnection, 1 }; } } std::span WorkflowConnection::GetDestinationPoints() { switch (ConnectionDirection) { case ManyToOne: return { &SingleConnection, 1 }; case OneToMany: return MultiConnections; } } std::span WorkflowConnection::GetDestinationPoints() const { switch (ConnectionDirection) { case ManyToOne: return { &SingleConnection, 1 }; case OneToMany: return MultiConnections; } } bool WorkflowNode::InputPin::IsConstantConnection() const { return ConnectionToConst && IsConnected(); } bool WorkflowNode::InputPin::IsConnected() const { return Connection != WorkflowConnection::kInvalidId; } BaseValue::Kind WorkflowNode::InputPin::GetMatchingType() const { return MatchingType; } WorkflowConnection::Direction WorkflowNode::InputPin::GetSupportedDirection() const { return AllowsMultipleConnections ? WorkflowConnection::ManyToOne : WorkflowConnection::OneToMany; } bool WorkflowNode::OutputPin::IsConnected() const { return Connection != WorkflowConnection::kInvalidId; } BaseValue::Kind WorkflowNode::OutputPin::GetMatchingType() const { return MatchingType; } WorkflowConnection::Direction WorkflowNode::OutputPin::GetSupportedDirection() const { return AllowsMultipleConnections ? WorkflowConnection::OneToMany : WorkflowConnection::ManyToOne; } WorkflowNode::WorkflowNode(Type type, Kind kind) : mType{ type } , mKind{ kind } { } size_t WorkflowNode::GetId() const { return mId; } WorkflowNode::Type WorkflowNode::GetType() const { return mType; } WorkflowNode::Kind WorkflowNode::GetKind() const { return mKind; } void WorkflowNode::ConnectInput(int nodeId, WorkflowNode& output, int outputNodeId) { mWorkflow->Connect(*this, nodeId, output, outputNodeId); } void WorkflowNode::DisconnectInput(int nodeId) { mWorkflow->DisconnectByDestination(*this, nodeId); } bool WorkflowNode::IsInputConnected(int nodeId) const { return mInputs[nodeId].IsConnected(); } void WorkflowNode::ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId) { mWorkflow->Connect(input, inputNodeId, *this, nodeId); } void WorkflowNode::DisconnectOutput(int nodeId) { mWorkflow->DisconnectBySource(*this, nodeId); } bool WorkflowNode::IsOutputConnected(int nodeId) const { return mOutputs[nodeId].IsConnected(); } WorkflowNode::InputPin& WorkflowNode::InsertInputPin(int atIdx) { assert(atIdx >= 0 && atIdx < mInputs.size()); mInputs.push_back(InputPin{}); for (int i = (int)mInputs.size() - 1, end = atIdx + 1; i >= end; --i) { SwapInputPin(i, i + 1); } return mInputs[atIdx]; } void WorkflowNode::RemoveInputPin(int pin) { DisconnectInput(pin); for (int i = 0, end = (int)mInputs.size() - 1; i < end; ++i) { SwapInputPin(i, i + 1); } mInputs.resize(mInputs.size() - 1); } void WorkflowNode::SwapInputPin(int a, int b) { auto& pinA = mInputs[a]; auto& pinB = mInputs[b]; if (mWorkflow) { // Delay assignment to ConnectionPoint::Pin so that the pinB loop (if it happens to run and looks in the same Connection) doesn't match the point updated in the pinA loop using Pt = WorkflowConnection::ConnectionPoint; Pt* pointA = nullptr; Pt* pointB = nullptr; if (pinA.IsConnected() && !pinA.IsConstantConnection()) { auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetDestinationPoints(); auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a }); pointA = it == pts.end() ? nullptr : &*it; } if (pinB.IsConnected() && !pinB.IsConstantConnection()) { auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetDestinationPoints(); auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b }); pointB = it == pts.end() ? nullptr : &*it; } if (pointA) pointA->Pin = b; if (pointB) pointB->Pin = a; } std::swap(pinA, pinB); } WorkflowNode::OutputPin& WorkflowNode::InsertOutputPin(int atIdx) { assert(atIdx >= 0 && atIdx < mOutputs.size()); mOutputs.push_back(OutputPin{}); for (int i = (int)mOutputs.size() - 1, end = atIdx + 1; i >= end; --i) { SwapOutputPin(i, i + 1); } return mOutputs[atIdx]; } void WorkflowNode::RemoveOutputPin(int pin) { DisconnectOutput(pin); for (int i = 0, end = (int)mOutputs.size() - 1; i < end; ++i) { SwapInputPin(i, i + 1); } mOutputs.resize(mOutputs.size() - 1); } void WorkflowNode::SwapOutputPin(int a, int b) { auto& pinA = mOutputs[a]; auto& pinB = mOutputs[b]; if (mWorkflow) { using Pt = WorkflowConnection::ConnectionPoint; Pt* pointA = nullptr; Pt* pointB = nullptr; if (pinA.IsConnected()) { auto pts = mWorkflow->GetConnectionById(pinA.Connection)->GetSourcePoints(); auto it = std::find(pts.begin(), pts.end(), Pt{ mId, a }); pointA = it == pts.end() ? nullptr : &*it; } if (pinB.IsConnected()) { auto pts = mWorkflow->GetConnectionById(pinB.Connection)->GetSourcePoints(); auto it = std::find(pts.begin(), pts.end(), Pt{ mId, b }); pointB = it == pts.end() ? nullptr : &*it; } if (pointA) pointA->Pin = b; if (pointB) pointB->Pin = a; } std::swap(pinA, pinB); } WorkflowConnection* Workflow::GetConnectionById(size_t id) { return &mConnections[id]; } WorkflowNode* Workflow::GetStepById(size_t id) { return mNodes[id].get(); } BaseValue* Workflow::GetConstantById(size_t id) { return mConstants[id].get(); } void Workflow::AddStep(std::unique_ptr step) { auto [storage, id] = AllocWorkflowStep(); storage = std::move(step); storage->OnAttach(*this, id); storage->mWorkflow = this; storage->mId = id; } void Workflow::RemoveStep(size_t id) { auto& step = mNodes[id]; if (step == nullptr) return; step->OnDetach(); step->mWorkflow = nullptr; step->mId = WorkflowNode::kInvalidId; } bool Workflow::Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin) { auto& src = sourceNode.mOutputs[sourcePin]; auto& dst = destinationNode.mInputs[destinationPin]; // Equivalent to `if (o.GetSupportedDirection() != i.GetSupportedDirection()) return false;` // Cannot connect two pins of different type if (src.AllowsMultipleConnections == dst.AllowsMultipleConnections) return false; // Would be same as `dst.GetSupportedDirection()` because we validated that they are the same above auto direction = src.GetSupportedDirection(); // TODO check type using Pt = WorkflowConnection::ConnectionPoint; Pt* srcConnPt; Pt* dstConnPt; size_t connId; switch (direction) { case WorkflowConnection::ManyToOne: { if (dst.IsConnected()) return false; WorkflowConnection* conn; if (src.IsConnected()) { conn = &mConnections[src.Connection]; connId = src.Connection; } else { auto p = AllocWorkflowConnection(); conn = &p.first; connId = p.second; } srcConnPt = &conn->MultiConnections.emplace_back(); dstConnPt = &conn->SingleConnection; } break; case WorkflowConnection::OneToMany: { if (src.IsConnected()) return false; WorkflowConnection* conn; if (dst.IsConnected()) { conn = &mConnections[src.Connection]; connId = src.Connection; } else { auto p = AllocWorkflowConnection(); conn = &p.first; connId = p.second; } srcConnPt = &conn->SingleConnection; dstConnPt = &conn->MultiConnections.emplace_back(); } break; } srcConnPt->Node = sourceNode.GetId(); srcConnPt->Pin = sourcePin; dstConnPt->Node = destinationNode.GetId(); dstConnPt->Pin = destinationPin; src.Connection = connId; dst.Connection = connId; return true; } bool Workflow::DisconnectBySource(WorkflowNode& sourceNode, int sourcePin) { auto& sp = sourceNode.mOutputs[sourcePin]; if (!sp.IsConnected()) return false; auto& conn = mConnections[sp.Connection]; using Pt = WorkflowConnection::ConnectionPoint; switch (sp.GetSupportedDirection()) { case WorkflowConnection::ManyToOne: { // Recessive pin, remove ConnectionPoint associated with this pin only auto& vec = conn.MultiConnections; vec.erase(std::remove(vec.begin(), vec.end(), Pt{ sourceNode.GetId(), sourcePin }), vec.end()); sp.Connection = WorkflowConnection::kInvalidId; } break; case WorkflowConnection::OneToMany: { // Dominate pin, removes whole connection for (auto& pt : conn.MultiConnections) { auto& node = *mNodes[pt.Node]; node.mInputs[pt.Pin].Connection = WorkflowConnection::kInvalidId; } sp.Connection = WorkflowConnection::kInvalidId; conn = {}; } break; } return true; } bool Workflow::DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin) { auto& dp = destinationNode.mInputs[destinationPin]; if (!dp.IsConnected()) return false; if (dp.IsConstantConnection()) { dp.ConnectionToConst = false; dp.Connection = WorkflowConnection::kInvalidId; return true; } auto& conn = mConnections[dp.Connection]; using Pt = WorkflowConnection::ConnectionPoint; switch (dp.GetSupportedDirection()) { case WorkflowConnection::ManyToOne: { // Dominate pin, removes whole connection for (auto& pt : conn.MultiConnections) { auto& node = *mNodes[pt.Node]; node.mOutputs[pt.Pin].Connection = WorkflowConnection::kInvalidId; } dp.Connection = WorkflowConnection::kInvalidId; conn = {}; } break; case WorkflowConnection::OneToMany: { // Recessive pin, remove ConnectionPoint associated with this pin only auto& vec = conn.MultiConnections; vec.erase(std::remove(vec.begin(), vec.end(), Pt{ destinationNode.GetId(), destinationPin }), vec.end()); dp.Connection = WorkflowConnection::kInvalidId; } break; } return true; } std::pair Workflow::AllocWorkflowConnection() { for (size_t idx = 0; idx < mConnections.size(); ++idx) { auto& elm = mConnections[idx]; if (!elm.IsValid()) { return { elm, idx }; } } auto id = mConnections.size(); return { mConnections.emplace_back(WorkflowConnection{}), id }; } std::pair&, size_t> Workflow::AllocWorkflowStep() { for (size_t idx = 0; idx < mNodes.size(); ++idx) { auto& elm = mNodes[idx]; if (elm == nullptr) { return { elm, idx }; } } auto id = mNodes.size(); return { mNodes.emplace_back(std::unique_ptr()), id }; } struct WorkflowEvaluationContext::RuntimeNode { WorkflowNode* Node; }; struct WorkflowEvaluationContext::RuntimeConnection { WorkflowConnection* Connection; std::unique_ptr 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(); } mConnections.resize(workflow.mConnections.size()); for (size_t i = 0; i < workflow.mConnections.size(); ++i) { mConnections[i].Connection = &workflow.mConnections[i]; } } BaseValue* WorkflowEvaluationContext::GetConnectionValue(size_t id, bool constant) { if (constant) { return mWorkflow->GetConstantById(id); } else { return mConnections[id].Value.get(); } } BaseValue* WorkflowEvaluationContext::GetConnectionValue(const WorkflowNode::InputPin& inputPin) { if (inputPin.IsConnected()) { return GetConnectionValue(inputPin.Connection, inputPin.IsConstantConnection()); } else { return nullptr; } } void WorkflowEvaluationContext::SetConnectionValue(size_t id, std::unique_ptr value) { mConnections[id].Value = std::move(value); } void WorkflowEvaluationContext::SetConnectionValue(const WorkflowNode::OutputPin& outputPin, std::unique_ptr value) { if (outputPin.IsConnected()) { SetConnectionValue(outputPin.Connection, std::move(value)); } } void WorkflowEvaluationContext::Run() { // TODO } void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node, int pinId, bool inputPin) { } void WorkflowEvaluationContext::ReportError(std::string message, const WorkflowNode& node) { } void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node, int pinId, bool inputPin) { } void WorkflowEvaluationContext::ReportWarning(std::string message, const WorkflowNode& node) { }