#pragma once #include "Model/Assets.hpp" #include "Model/Workflow/Value.hpp" #include "Utils/Vector.hpp" #include "cplt_fwd.hpp" #include #include #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) const; }; 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, }; enum Category { CG_Numeric, CG_Text, CG_Document, CG_UserInput, CG_SystemInput, CG_Output, InvalidCategory, CategoryCount = InvalidCategory, }; 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* FormatCategory(Category category); static const char* FormatType(Type type); static Category QueryCategory(Kind kind); static std::span QueryCategoryMembers(Category category); static std::unique_ptr CreateByKind(Kind kind); static bool IsInstance(const WorkflowNode* node); 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 : public Asset { friend class WorkflowNode; friend class WorkflowEvaluationContext; class Private; public: using CategoryType = WorkflowAssetList; static constinit const WorkflowAssetList Category; private: 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 */ enum GraphUpdateResult { /// Successfully rebuilt graph dependent data. /// Details: nothing is written. GUR_Success, /// Nothing has changed since last time UpdateGraph() was called. /// Details: nothing is written. GUR_NoWorkToDo, /// Details: list of nodes is written. GUR_UnsatisfiedDependencies, /// Details: list of nodes is written. GUR_UnreachableNodes, }; using GraphUpdateDetails = std::variant< // Case: nothing std::monostate, // Case: list of nodes (ids) std::vector>; GraphUpdateResult UpdateGraph(GraphUpdateDetails* details = nullptr); /* Serialization */ void ReadFromDataStream(InputDataStream& stream); void WriteToDataStream(OutputDataStream& stream) const; private: std::pair AllocWorkflowConnection(); std::pair&, uint32_t> AllocWorkflowStep(); }; class WorkflowAssetList final : public AssetListTyped { private: // AC = Asset Creator std::string mACNewName; NameSelectionError mACNewNameError = NameSelectionError::Empty; public: // Inherit constructors using AssetListTyped::AssetListTyped; protected: void DiscoverFiles(const std::function& callback) const override; std::string RetrieveNameFromFile(const std::filesystem::path& file) const override; uuids::uuid RetrieveUuidFromFile(const std::filesystem::path& file) const override; std::filesystem::path RetrievePathFromAsset(const SavedAsset& asset) const override; bool SaveInstance(const SavedAsset& assetInfo, const Asset* asset) const override; Workflow* LoadInstance(const SavedAsset& assetInfo) const override; Workflow* CreateInstance(const SavedAsset& assetInfo) const override; bool RenameInstanceOnDisk(const SavedAsset& assetInfo, std::string_view oldName) const override; void DisplayAssetCreator(ListState& state) override; void DisplayDetailsTable(ListState& state) const override; };