#pragma once #include "Model/Workflow/Value.hpp" #include "Utils/Vector.hpp" #include "cplt_fwd.hpp" #include #include #include #include #include #include #include #include #include class WorkflowConnection { public: static constexpr auto kInvalidId = std::numeric_limits::max(); enum Direction { ManyToOne, OneToMany, }; struct ConnectionPoint { size_t Node; int Pin; bool operator==(const ConnectionPoint&) const = default; }; std::vector MultiConnections; ConnectionPoint SingleConnection; Direction ConnectionDirection; public: WorkflowConnection(); bool IsValid() const; std::span GetSourcePoints(); 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 { public: static constexpr auto kInvalidId = std::numeric_limits::max(); 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 InputPin { size_t Connection = WorkflowConnection::kInvalidId; BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool ConnectionToConst = false; bool AllowsMultipleConnections = 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; BaseValue::Kind GetMatchingType() const; WorkflowConnection::Direction GetSupportedDirection() const; }; struct OutputPin { size_t Connection = WorkflowConnection::kInvalidId; BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool AllowsMultipleConnections = false; bool IsConnected() const; BaseValue::Kind GetMatchingType() const; WorkflowConnection::Direction GetSupportedDirection() const; }; friend class Workflow; friend class WorkflowEvaluationContext; Workflow* mWorkflow; size_t mId; std::vector mInputs; std::vector mOutputs; Vec2i mPosition; 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; WorkflowNode(const WorkflowNode&) = delete; WorkflowNode& operator=(const WorkflowNode&) = delete; WorkflowNode(WorkflowNode&&) = default; WorkflowNode& operator=(WorkflowNode&&) = default; void SetPosition(const Vec2i& position); Vec2i GetPosition() const; size_t GetId() 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; void ConnectOutput(int nodeId, WorkflowNode& input, int inputNodeId); void DisconnectOutput(int nodeId); bool IsOutputConnected(int nodeId) const; 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); 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 mConnections; std::vector> mNodes; std::vector> mConstants; std::vector> mDepthGroups; int mConnectionCount; int mNodeCount; int mConstantCount; bool mDepthsDirty = true; public: WorkflowConnection* GetConnectionById(size_t id); WorkflowNode* GetStepById(size_t id); BaseValue* GetConstantById(size_t id); void AddStep(std::unique_ptr step); void RemoveStep(size_t id); void RemoveConnection(size_t id); bool Connect(WorkflowNode& sourceNode, int sourcePin, WorkflowNode& destinationNode, int destinationPin); bool DisconnectBySource(WorkflowNode& sourceNode, int sourcePin); bool DisconnectByDestination(WorkflowNode& destinationNode, int destinationPin); const std::vector>& GetDepthGroups() const; bool DoesDepthNeedsUpdate() const; struct GraphUpdate_Success {}; struct GraphUpdate_NoWorkToDo {}; struct GraphUpdate_UnsatisfiedDependencies { std::vector UnsatisfiedNodes; }; struct GraphUpdate_UnreachableNodes { std::vector UnreachableNodes; }; using GraphUpdateResult = std::variant< GraphUpdate_Success, GraphUpdate_NoWorkToDo, GraphUpdate_UnsatisfiedDependencies, GraphUpdate_UnreachableNodes>; /// 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(); };