#pragma once #include "Model/Workflow/Value.hpp" #include "Utils/Vector.hpp" #include "cplt_fwd.hpp" #include #include #include #include #include #include #include #include #include #include namespace ImNodes = ax::NodeEditor; class WorkflowConnection { public: static constexpr auto kInvalidId = std::numeric_limits::max(); uint32_t Id; uint32_t SourceNode; uint32_t SourcePin; uint32_t DestinationNode; uint32_t DestinationPin; public: WorkflowConnection(); bool IsValid() const; /// Used for `LinkId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). ImNodes::LinkId GetLinkId() 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(); static constexpr auto kInvalidPinId = 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, }; struct InputPin { uint32_t Connection = WorkflowConnection::kInvalidId; BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool ConnectionToConst = false; /// A constant connection connects from a user-specified constant value, feeding to a valid \c DestinationNode and \c DestinationPin (i.e. input pins). bool IsConstantConnection() const; bool IsConnected() const; BaseValue::Kind GetMatchingType() const; }; struct OutputPin { uint32_t Connection = WorkflowConnection::kInvalidId; BaseValue::Kind MatchingType = BaseValue::InvalidKind; bool IsConnected() const; BaseValue::Kind GetMatchingType() const; }; protected: friend class Workflow; friend class WorkflowEvaluationContext; Workflow* mWorkflow; std::vector mInputs; std::vector mOutputs; Vec2i mPosition; uint32_t mId; Kind mKind; int mDepth; bool mLocked; public: static const char* FormatKind(Kind kind); static const char* FormatType(Type type); static std::unique_ptr CreateByKind(Kind kind); WorkflowNode(Kind kind, bool locked); 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; uint32_t GetId() const; /// Used for `NodeId` when interfacing with imgui node editor. Runtime only (not saved to disk and generated when loading). ImNodes::NodeId GetNodeId() const; Kind GetKind() const; int GetDepth() const; bool IsLocked() const; Type GetType() const; bool IsInputNode() const; bool IsOutputNode() const; void ConnectInput(uint32_t pinId, WorkflowNode& srcNode, uint32_t srcPinId); void DisconnectInput(uint32_t pinId); void DrawInputPinDebugInfo(uint32_t pinId) const; const InputPin& GetInputPin(uint32_t pinId)const ; ImNodes::PinId GetInputPinUniqueId(uint32_t pinId) const; void ConnectOutput(uint32_t pinId, WorkflowNode& dstNode, uint32_t dstPinId); void DisconnectOutput(uint32_t pinId); void DrawOutputPinDebugInfo(uint32_t pinId)const; const OutputPin& GetOutputPin(uint32_t pinId)const ; ImNodes::PinId GetOutputPinUniqueId(uint32_t pinId) 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, uint32_t newId); void OnDetach(); }; class Workflow { private: friend class WorkflowNode; 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: /* Graph access */ const std::vector& GetConnections() const; std::vector& GetConnections(); const std::vector>& GetNodes() const; std::vector>& GetNodes(); const std::vector>& GetConstants() const; std::vector>& GetConstants(); WorkflowConnection* GetConnectionById(uint32_t id); WorkflowConnection* GetConnectionByLinkId(ImNodes::LinkId linkId); WorkflowNode* GetNodeById(uint32_t id); WorkflowNode* GetNodeByNodeId(ImNodes::NodeId nodeId); BaseValue* GetConstantById(uint32_t id); struct GlobalPinId { WorkflowNode* Node; uint32_t PinId; /// true => input pin /// false => output pin bool IsOutput; }; /// `pinId` should be the `UniqueId` of a pin from a node that's within this workflow. GlobalPinId DisassembleGlobalPinId(ImNodes::PinId id); ImNodes::PinId FabricateGlobalPinId(const WorkflowNode& node, uint32_t pinId, bool isOutput) const; const std::vector>& GetDepthGroups() const; bool DoesDepthNeedsUpdate() const; /* Graph mutation */ void AddNode(std::unique_ptr step); void RemoveNode(uint32_t id); void RemoveConnection(uint32_t id); bool Connect(WorkflowNode& sourceNode, uint32_t sourcePin, WorkflowNode& destinationNode, uint32_t destinationPin); bool DisconnectBySource(WorkflowNode& sourceNode, uint32_t sourcePin); bool DisconnectByDestination(WorkflowNode& destinationNode, uint32_t destinationPin); /* Graph rebuild */ struct GraphUpdateResult { struct Success { }; struct NoWorkToDo { }; struct UnsatisfiedDependencies { std::vector UnsatisfiedNodes; }; struct UnreachableNodes { std::vector UnreachableNodes; }; using T = std::variant< Success, NoWorkToDo, UnsatisfiedDependencies, UnreachableNodes>; }; GraphUpdateResult::T UpdateGraph(); /* Serialization */ enum ReadResult { ReadSuccess, ReadInvalidVersion, }; ReadResult ReadFrom(std::istream& stream); void WriteTo(std::ostream& stream); private: std::pair AllocWorkflowConnection(); std::pair&, size_t> AllocWorkflowStep(); };