From c6dfc3ca513af2a0e8e7f430e63f5cd8ce17ba88 Mon Sep 17 00:00:00 2001 From: RobBuchanan Date: Fri, 12 Jun 2026 12:26:13 +0100 Subject: [PATCH] dynamic outputs 1 --- src/nodes/node.cpp | 7 ++++ src/nodes/node.h | 40 +++++++++++++++++++++++ src/nodes/parameter.h | 1 + src/nodes/test.cpp | 14 ++++++++ src/nodes/test.h | 8 +++++ tests/graphData.h | 16 +++++++++ tests/nodes/parameters.cpp | 66 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 152 insertions(+) diff --git a/src/nodes/node.cpp b/src/nodes/node.cpp index a566c7abbd..a926a9d5a8 100644 --- a/src/nodes/node.cpp +++ b/src/nodes/node.cpp @@ -140,6 +140,7 @@ NodeConstants::ProcessResult Node::run() case (NodeConstants::ProcessResult::Failed): break; case (NodeConstants::ProcessResult::Success): + updateDynamicOutputs(); ++versionIndex_; upToDate_ = true; break; @@ -282,6 +283,12 @@ std::shared_ptr Node::findOption(std::string_view optionName) con return options_.at(std::string{optionName}); } +// Update dynamic outputs +void Node::updateDynamicOutputs() { registerDynamicOutputs(); } + +// Register dynamic outputs +void Node::registerDynamicOutputs() {} + // Return Options Node::NodeParameterMap &Node::options() { return options_; }; diff --git a/src/nodes/node.h b/src/nodes/node.h index 662256b2b9..146008b79a 100644 --- a/src/nodes/node.h +++ b/src/nodes/node.h @@ -300,6 +300,46 @@ class Node : public Serialisable<> } // Return named option if it exists std::shared_ptr findOption(std::string_view name) const; + // Update dynamic outputs + void updateDynamicOutputs(); + // Register dynamic output from container data source + template + void registerDynamicOutput(std::vector &data, std::string description, + const std::variant> &name = std::string("dynOut")) + { + for (int i = 0; i < data.size(); i++) + { + auto val = data[i]; + auto paramName = std::holds_alternative(name) ? (std::get(name) + std::format("-{}", i)) + : std::get>(name)(val); + + // Check if output already exists - do not add if it does + if (outputs_.find(paramName) != outputs_.end()) + continue; + + addOutput(paramName, description, data[i]); + } + } + template + void + registerDynamicPointerOutput(std::vector &data, std::string description, + const std::variant> &name = std::string("dynOut")) + { + for (int i = 0; i < data.size(); i++) + { + auto val = data[i]; + auto paramName = std::holds_alternative(name) ? (std::get(name) + std::format("-{}", i)) + : std::get>(name)(val); + + // Check if output already exists - do not add if it does + if (outputs_.find(paramName) != outputs_.end()) + continue; + + addPointerOutput(paramName, description, data[i]); + } + } + // Register dynamic outputs + virtual void registerDynamicOutputs(); // Return options NodeParameterMap &options(); // Set option value diff --git a/src/nodes/parameter.h b/src/nodes/parameter.h index 805a118ce3..d0004ecfbd 100644 --- a/src/nodes/parameter.h +++ b/src/nodes/parameter.h @@ -53,6 +53,7 @@ class ParameterBase : public Serialisable<> ClearData, /* Indicates that any local data should be cleared if the parameter is changed */ Input, /* Indicates that the parameter is meant to be a sink for data and not a source */ Output, /* Indicates that the parameter is meant to be a source of data and not a sink */ + Dynamic, /* Indicates that the parameter is meant to be a dynamic source of data and not a sink */ }; // Allowed Edge Count enum AllowedEdgeCount diff --git a/src/nodes/test.cpp b/src/nodes/test.cpp index 3341632115..96ca88cb85 100644 --- a/src/nodes/test.cpp +++ b/src/nodes/test.cpp @@ -12,6 +12,9 @@ TestNode::TestNode(Graph *parentGraph) : Node(parentGraph) addInput("NumberVector", "A vector of numbers", numberVector_); addInput("OptionalNumber", "A single number", optionalNumber_); addInput("Variant", "A variant", variant_); + addInput("Message", "A message", message_); + addInput("Char", "A character", char_); + addInput("CharPtr", "A character", charPtr_); // Outputs addOutput("Configuration", "A configuration output", configuration_); @@ -42,6 +45,13 @@ TestNode::TestVariant TestNode::variant() { return variant_; } * Processing */ +// Register dynamic outputs +void TestNode::registerDynamicOutputs() +{ + registerDynamicOutput(messageParts_, "Individual character from a message", std::string("Message-Part")); + registerDynamicPointerOutput(messageParts_, "Individual character from a message", std::string("Message-Ptr-Part")); +} + // Perform processing NodeConstants::ProcessResult TestNode::process() { @@ -54,5 +64,9 @@ NodeConstants::ProcessResult TestNode::process() else optionalConfiguration_ = std::nullopt; + // Standard dynamic outputs + messageParts_.clear(); + messageParts_.insert(messageParts_.end(), message_.begin(), message_.end()); + return NodeConstants::ProcessResult::Success; } diff --git a/src/nodes/test.h b/src/nodes/test.h index 1cb3be09da..bc39cbe0eb 100644 --- a/src/nodes/test.h +++ b/src/nodes/test.h @@ -33,6 +33,11 @@ class TestNode : public Node // Variant using TestVariant = VariantParameterData; TestVariant variant_; + // Test string + char char_; + char *charPtr_; + std::string message_; + std::vector messageParts_; public: // Return type of the node @@ -53,6 +58,9 @@ class TestNode : public Node /* * Processing */ + private: + void registerDynamicOutputs() override; + protected: // Perform processing NodeConstants::ProcessResult process() override; diff --git a/tests/graphData.h b/tests/graphData.h index 8a551f23e5..8cbb670170 100644 --- a/tests/graphData.h +++ b/tests/graphData.h @@ -59,6 +59,22 @@ class TestGraph : public DissolveGraph std::string fetchHeadName() const { return head_ ? std::string(head_->name()) : "NO_NODE"; } // Returns reference to current top node in graph, cast to the known node type template NodeType *head() const { return static_cast(head_); } + // Run the graph in a piecewise manner - initially from a specific node, then from the last node - in order to emplace a set + // of dynamic edges that we expect to exist at run time + NodeConstants::ProcessResult runDynamic(Node *startNode, std::vector edges) + { + setUpdateRequired(); + + auto result = NodeConstants::ProcessResult::Unchanged; + result = startNode->run(); + if (result == NodeConstants::ProcessResult::Failed) + return result; + + for (const auto &edge : edges) + if (!addEdge(edge) || findNode(edge.targetNode)->run() == NodeConstants::ProcessResult::Failed) + return NodeConstants::ProcessResult::Failed; + return result; + } // Append new node to the graph Node *appendNode(const std::string &nodeType, const std::optional &name = {}) { diff --git a/tests/nodes/parameters.cpp b/tests/nodes/parameters.cpp index 96b9e6e0a1..4b479053f8 100644 --- a/tests/nodes/parameters.cpp +++ b/tests/nodes/parameters.cpp @@ -360,4 +360,70 @@ TEST_F(ParametersTest, OptionalPointerToVariant) EXPECT_EQ(std::get(b_->variant().data), &a_->optionalConfiguration().value()); } +TEST_F(ParametersTest, DynamicOutput) +{ + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "Sender")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverA")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverB")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverC")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverD")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverE")); + + auto sender = testGraph_.findNode("Sender"); + ASSERT_TRUE(sender->setInput("Message", std::string("hello"))); + ASSERT_EQ(testGraph_.runDynamic(sender, + { + {"Sender", "Message-Part-0", "RecieverA", "Char"}, + {"Sender", "Message-Part-1", "RecieverB", "Char"}, + {"Sender", "Message-Part-2", "RecieverC", "Char"}, + {"Sender", "Message-Part-3", "RecieverD", "Char"}, + {"Sender", "Message-Part-4", "RecieverE", "Char"}, + + }), + NodeConstants::ProcessResult::Success); + + std::vector chars; + for (const auto &which : {"A", "B", "C", "D", "E"}) + { + auto node = testGraph_.findNode("Reciever" + std::string(which)); + chars.push_back(node->findInput("Char")->get()); + } + + std::string message(chars.begin(), chars.end()); + ASSERT_EQ(message, "hello"); +} + +TEST_F(ParametersTest, DynamicPointerOutput) +{ + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "Sender")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverA")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverB")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverC")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverD")); + ASSERT_TRUE(testGraph_.addNode(std::make_unique(&testGraph_), "RecieverE")); + + auto sender = testGraph_.findNode("Sender"); + ASSERT_TRUE(sender->setInput("Message", std::string("hello"))); + ASSERT_EQ(testGraph_.runDynamic(sender, + { + {"Sender", "Message-Ptr-Part-0", "RecieverA", "CharPtr"}, + {"Sender", "Message-Ptr-Part-1", "RecieverB", "CharPtr"}, + {"Sender", "Message-Ptr-Part-2", "RecieverC", "CharPtr"}, + {"Sender", "Message-Ptr-Part-3", "RecieverD", "CharPtr"}, + {"Sender", "Message-Ptr-Part-4", "RecieverE", "CharPtr"}, + + }), + NodeConstants::ProcessResult::Success); + + std::vector chars; + for (const auto &which : {"A", "B", "C", "D", "E"}) + { + auto node = testGraph_.findNode("Reciever" + std::string(which)); + chars.push_back(*node->findInput("CharPtr")->get()); + } + + std::string message(chars.begin(), chars.end()); + ASSERT_EQ(message, "hello"); +} + } // namespace UnitTest