Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/nodes/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ NodeConstants::ProcessResult Node::run()
case (NodeConstants::ProcessResult::Failed):
break;
case (NodeConstants::ProcessResult::Success):
updateDynamicOutputs();
++versionIndex_;
upToDate_ = true;
break;
Expand Down Expand Up @@ -282,6 +283,12 @@ std::shared_ptr<ParameterBase> 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_; };

Expand Down
40 changes: 40 additions & 0 deletions src/nodes/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,46 @@ class Node : public Serialisable<>
}
// Return named option if it exists
std::shared_ptr<ParameterBase> findOption(std::string_view name) const;
// Update dynamic outputs
void updateDynamicOutputs();
// Register dynamic output from container data source
template <typename T>
void registerDynamicOutput(std::vector<T> &data, std::string description,
const std::variant<std::string, std::function<std::string(T)>> &name = std::string("dynOut"))
{
for (int i = 0; i < data.size(); i++)
{
auto val = data[i];
auto paramName = std::holds_alternative<std::string>(name) ? (std::get<std::string>(name) + std::format("-{}", i))
: std::get<std::function<std::string(T)>>(name)(val);

// Check if output already exists - do not add if it does
if (outputs_.find(paramName) != outputs_.end())
continue;

addOutput<T>(paramName, description, data[i]);
}
}
template <typename T>
void
registerDynamicPointerOutput(std::vector<T> &data, std::string description,
const std::variant<std::string, std::function<std::string(T)>> &name = std::string("dynOut"))
{
for (int i = 0; i < data.size(); i++)
{
auto val = data[i];
auto paramName = std::holds_alternative<std::string>(name) ? (std::get<std::string>(name) + std::format("-{}", i))
: std::get<std::function<std::string(T)>>(name)(val);

// Check if output already exists - do not add if it does
if (outputs_.find(paramName) != outputs_.end())
continue;

addPointerOutput<T>(paramName, description, data[i]);
}
}
// Register dynamic outputs
virtual void registerDynamicOutputs();
// Return options
NodeParameterMap &options();
// Set option value
Expand Down
1 change: 1 addition & 0 deletions src/nodes/parameter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions src/nodes/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
Expand Down Expand Up @@ -42,6 +45,13 @@ TestNode::TestVariant TestNode::variant() { return variant_; }
* Processing
*/

// Register dynamic outputs
void TestNode::registerDynamicOutputs()
{
registerDynamicOutput<char>(messageParts_, "Individual character from a message", std::string("Message-Part"));
registerDynamicPointerOutput<char>(messageParts_, "Individual character from a message", std::string("Message-Ptr-Part"));
}

// Perform processing
NodeConstants::ProcessResult TestNode::process()
{
Expand All @@ -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;
}
8 changes: 8 additions & 0 deletions src/nodes/test.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ class TestNode : public Node
// Variant
using TestVariant = VariantParameterData<Structure, Number, std::string, Configuration *>;
TestVariant variant_;
// Test string
char char_;
char *charPtr_;
std::string message_;
std::vector<char> messageParts_;

public:
// Return type of the node
Expand All @@ -53,6 +58,9 @@ class TestNode : public Node
/*
* Processing
*/
private:
void registerDynamicOutputs() override;

protected:
// Perform processing
NodeConstants::ProcessResult process() override;
Expand Down
16 changes: 16 additions & 0 deletions tests/graphData.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <class NodeType> NodeType *head() const { return static_cast<NodeType *>(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<EdgeDefinition> 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<std::string> &name = {})
{
Expand Down
66 changes: 66 additions & 0 deletions tests/nodes/parameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -360,4 +360,70 @@ TEST_F(ParametersTest, OptionalPointerToVariant)
EXPECT_EQ(std::get<Configuration *>(b_->variant().data), &a_->optionalConfiguration().value());
}

TEST_F(ParametersTest, DynamicOutput)
{
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "Sender"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverA"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverB"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverC"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverD"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&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<char> 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<char>());
}

std::string message(chars.begin(), chars.end());
ASSERT_EQ(message, "hello");
}

TEST_F(ParametersTest, DynamicPointerOutput)
{
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "Sender"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverA"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverB"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverC"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&testGraph_), "RecieverD"));
ASSERT_TRUE(testGraph_.addNode(std::make_unique<TestNode>(&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<char> 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<char *>());
}

std::string message(chars.begin(), chars.end());
ASSERT_EQ(message, "hello");
}

} // namespace UnitTest
Loading