Unverified Commit c476f698 authored by Floris Berendsen's avatar Floris Berendsen Committed by GitHub
Browse files

Merge pull request #152 from SuperElastix/SELX-150-Blueprint-reader-cannot-handle-two-connections

Selx 150 blueprint reader cannot handle two connections
parents 0726685f 6d47f028
......@@ -55,6 +55,8 @@ public:
typedef std::map< ParameterKeyType, ParameterValueType > ParameterMapType;
typedef std::string ComponentNameType;
typedef std::vector< ComponentNameType > ComponentNamesType;
typedef std::string ConnectionNameType;
typedef std::vector< ConnectionNameType > ConnectionNamesType;
/* m_Blueprint is initialized in the default constructor */
Blueprint();
......@@ -76,13 +78,13 @@ public:
// Returns a vector of the all Component names in the graph.
ComponentNamesType GetComponentNames( void ) const;
bool SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap );
bool SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap, ConnectionNameType name = "" );
ParameterMapType GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const;
ParameterMapType GetConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name = "" ) const;
bool DeleteConnection( ComponentNameType upstream, ComponentNameType downstream );
bool DeleteConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name = "" );
bool ConnectionExists( ComponentNameType upstream, ComponentNameType downstream ) const;
bool ConnectionExists( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name = "" ) const;
//std::unique_ptr<BlueprintImpl> Clone(BlueprintImpl const &other );
......
......@@ -84,27 +84,27 @@ Blueprint::GetComponentNames( void ) const
bool
Blueprint
::SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap )
::SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap, ConnectionNameType name )
{
this->Modified();
return this->m_BlueprintImpl->SetConnection( upstream, downstream, parameterMap );
return this->m_BlueprintImpl->SetConnection( upstream, downstream, parameterMap, name );
}
Blueprint::ParameterMapType
Blueprint
::GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const
::GetConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name ) const
{
return this->m_BlueprintImpl->GetConnection( upstream, downstream );
return this->m_BlueprintImpl->GetConnection( upstream, downstream, name );
}
bool
Blueprint
::DeleteConnection( ComponentNameType upstream, ComponentNameType downstream )
::DeleteConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name )
{
this->Modified();
return this->m_BlueprintImpl->DeleteConnection( upstream, downstream );
return this->m_BlueprintImpl->DeleteConnection( upstream, downstream, name );
}
......@@ -118,9 +118,9 @@ Blueprint
bool
Blueprint
::ConnectionExists( ComponentNameType upstream, ComponentNameType downstream ) const
::ConnectionExists( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name ) const
{
return this->m_BlueprintImpl->ConnectionExists( upstream, downstream );
return this->m_BlueprintImpl->ConnectionExists( upstream, downstream, name );
}
......
......@@ -161,7 +161,7 @@ BlueprintImpl
bool
BlueprintImpl
::SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap )
::SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap, ConnectionNameType name )
{
if( !this->ComponentExists( upstream ) || !this->ComponentExists( downstream ) )
{
......@@ -169,37 +169,72 @@ BlueprintImpl
return false;
}
if( !this->ConnectionExists( upstream, downstream ) )
{
boost::add_edge_by_label( upstream, downstream, { parameterMap }, this->m_Graph );
}
else
{
this->m_Graph[ this->GetConnectionIndex( upstream, downstream ) ].parameterMap = parameterMap;
}
// Multiple parallel connections are allowed. If a connection with the name "name" exists it should be overridden, otherwise just added.
boost::graph_traits<GraphType>::out_edge_iterator ei, ei_end;
// too bad edge_range_by_label doesn't exist
boost::tie(ei, ei_end) = boost::edge_range(this->m_Graph.vertex(upstream), this->m_Graph.vertex(downstream), this->m_Graph.graph());
for (; ei != ei_end; ++ei)
{
auto existingName = boost::get(&ConnectionPropertyType::name, this->m_Graph.graph(), *ei);
if (name == existingName)
{
// override previous parameterMap
boost::put(&ConnectionPropertyType::parameterMap, this->m_Graph.graph(), *ei, parameterMap);
return true;
}
}// no existing connections named "name" were found.
boost::add_edge_by_label(upstream, downstream, { name, parameterMap }, this->m_Graph);
return true;
}
BlueprintImpl::ParameterMapType
BlueprintImpl
::GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const
::GetConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name ) const
{
return this->m_Graph[ this->GetConnectionIndex( upstream, downstream ) ].parameterMap;
}
// This function assumes the connection exists.
boost::graph_traits<GraphType>::out_edge_iterator ei, ei_end;
// too bad edge_range_by_label doesn't exist
boost::tie(ei, ei_end) = boost::edge_range(this->m_Graph.vertex(upstream), this->m_Graph.vertex(downstream), this->m_Graph.graph());
for (; ei != ei_end; ++ei)
{
auto existingName = boost::get(&ConnectionPropertyType::name, this->m_Graph.graph(), *ei);
if (name == existingName)
{
return boost::get(&ConnectionPropertyType::parameterMap, this->m_Graph.graph(), *ei);
}
} // no existing connections named "name" were found.
throw std::runtime_error( "BlueprintImpl does not contain connection from component " + upstream + " to " + downstream + " by name " + name );
// assert(false);
return ParameterMapType();
}
bool
BlueprintImpl
::DeleteConnection( BlueprintImpl::ComponentNameType upstream, BlueprintImpl::ComponentNameType downstream )
::DeleteConnection( BlueprintImpl::ComponentNameType upstream, BlueprintImpl::ComponentNameType downstream, ConnectionNameType name )
{
if( this->ConnectionExists( upstream, downstream ) )
{
boost::remove_edge_by_label( upstream, downstream, this->m_Graph );
if( this->ConnectionExists( upstream, downstream, name ) )
{
boost::graph_traits<GraphType>::out_edge_iterator ei, ei_end;
// too bad edge_range_by_label doesn't exist
boost::tie(ei, ei_end) = boost::edge_range(this->m_Graph.vertex(upstream), this->m_Graph.vertex(downstream), this->m_Graph.graph());
for (; ei != ei_end; ++ei) {
auto existingName = boost::get(&ConnectionPropertyType::name, this->m_Graph.graph(), *ei);
if (name == existingName)
{
boost::remove_edge(*ei, this->m_Graph.graph());
return true;
}
}
}
return !this->ConnectionExists( upstream, downstream );
return false;
}
......@@ -213,7 +248,7 @@ BlueprintImpl
bool
BlueprintImpl
::ConnectionExists( ComponentNameType upstream, ComponentNameType downstream ) const
::ConnectionExists( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name ) const
{
if( !this->ComponentExists( upstream ) )
{
......@@ -224,7 +259,23 @@ BlueprintImpl
return false;
}
return boost::edge_by_label( upstream, downstream, this->m_Graph ).second;
if (boost::edge_by_label( upstream, downstream, this->m_Graph ).second)
{
boost::graph_traits<GraphType>::out_edge_iterator ei, ei_end;
// too bad edge_range_by_label doesn't exist
boost::tie(ei, ei_end) = boost::edge_range(this->m_Graph.vertex(upstream), this->m_Graph.vertex(downstream), this->m_Graph.graph());
for (; ei != ei_end; ++ei)
{
auto existingName = boost::get(&ConnectionPropertyType::name, this->m_Graph.graph(), *ei);
if (name == existingName)
{
return true;
}
}
} // no existing connections named "name" were found.
return false;
}
......@@ -294,60 +345,64 @@ BlueprintImpl
{
for( auto incomingName : other.GetInputNames( componentName ) )
{
// Does other blueprint have a connection that already exists?
if( this->ConnectionExists( incomingName, componentName ) )
for (auto connectionName : other.GetConnectionNames(incomingName, componentName ) )
{
// Connection exists, check if properties can be merged
auto ownProperties = this->GetConnection( incomingName, componentName );
auto othersProperties = other.GetConnection( incomingName, componentName );
for( auto const & othersEntry : othersProperties )
// Does other blueprint have a connection that already exists?
if (this->ConnectionExists(incomingName, componentName, connectionName ))
{
// Does other use a property key that already exists in this component?
if( ownProperties.count( othersEntry.first ) )
// Connection exists, check if properties can be merged
auto ownProperties = this->GetConnection( incomingName, componentName, connectionName );
auto othersProperties = other.GetConnection( incomingName, componentName, connectionName );
for( auto const & othersEntry : othersProperties )
{
auto && ownValues = ownProperties[ othersEntry.first ];
auto && otherValues = othersEntry.second;
// Are the property values equal?
if( ownValues.size() != otherValues.size() )
// Does other use a property key that already exists in this component?
if( ownProperties.count( othersEntry.first ) )
{
// No, based on the number of values we see that it is different. Blueprints cannot be Composed
this->m_Graph = graph_backup;
return false;
}
else
{
ParameterValueType::const_iterator ownValue;
ParameterValueType::const_iterator otherValue;
for( ownValue = ownValues.begin(), otherValue = otherValues.begin(); ownValue != ownValues.end(); ++ownValue, ++otherValue )
auto && ownValues = ownProperties[ othersEntry.first ];
auto && otherValues = othersEntry.second;
// Are the property values equal?
if( ownValues.size() != otherValues.size() )
{
if( *otherValue != *ownValue )
// No, based on the number of values we see that it is different. Blueprints cannot be Composed
this->m_Graph = graph_backup;
return false;
}
else
{
ParameterValueType::const_iterator ownValue;
ParameterValueType::const_iterator otherValue;
for( ownValue = ownValues.begin(), otherValue = otherValues.begin(); ownValue != ownValues.end(); ++ownValue, ++otherValue )
{
// No, at least one value is different. Blueprints cannot be Composed
this->m_Graph = graph_backup;
return false;
if( *otherValue != *ownValue )
{
// No, at least one value is different. Blueprints cannot be Composed
this->m_Graph = graph_backup;
return false;
}
}
}
}
}
else
{
// Property key doesn't exist yet, add entry to this component
auto ownProperties = this->GetConnection( incomingName, componentName );
ownProperties[ othersEntry.first ] = othersEntry.second;
this->SetConnection( incomingName, componentName, ownProperties );
}
else
{
// Property key doesn't exist yet, add entry to this component
auto ownProperties = this->GetConnection( incomingName, componentName, connectionName );
ownProperties[ othersEntry.first ] = othersEntry.second;
this->SetConnection( incomingName, componentName, ownProperties, connectionName );
return true;
}
} // end loop otherProperties
}
else // connection with connectionName does not exist
{
// Create Connection copying properties of other
this->SetConnection( incomingName, componentName, other.GetConnection( incomingName, componentName, connectionName ), connectionName );
}
}
else
{
// Create Component copying properties of other
this->SetConnection( incomingName, componentName, other.GetConnection( incomingName, componentName ) );
}
}
}
return true;
}
BlueprintImpl::ComponentNamesType
......@@ -399,21 +454,25 @@ BlueprintImpl
}
BlueprintImpl::ConnectionIndexType
BlueprintImpl::ConnectionNamesType
BlueprintImpl
::GetConnectionIndex( ComponentNameType upstream, ComponentNameType downstream ) const
::GetConnectionNames(const ComponentNameType upstream, const ComponentNameType downstream) const
{
// This function is part of the internal API and should fail hard if we use it incorrectly
if( !this->ConnectionExists( upstream, downstream ) )
ConnectionNamesType container;
boost::graph_traits<GraphType>::out_edge_iterator ei, ei_end;
// too bad edge_range_by_label doesn't exist
boost::tie(ei, ei_end) = boost::edge_range(this->m_Graph.vertex(upstream), this->m_Graph.vertex(downstream), this->m_Graph.graph());
for (; ei != ei_end; ++ei)
{
this->m_LoggerImpl->Log(LogLevel::CRT, "BlueprintImpl does not contain connection from component {} to {}.", upstream, downstream );
throw std::runtime_error( "BlueprintImpl does not contain connection from component " + upstream + " to " + downstream );
container.push_back(boost::get(&ConnectionPropertyType::name, this->m_Graph.graph(), *ei));
}
return boost::edge_by_label( upstream, downstream, this->m_Graph ).first;
return container;
}
void
BlueprintImpl
::Write( const std::string filename )
......@@ -668,10 +727,7 @@ BlueprintImpl::MergeProperties(const PropertyTreeType & pt)
BOOST_FOREACH(const PropertyTreeType::value_type & v, pt.equal_range("Connection"))
{
std::string connectionName = v.second.data();
if (connectionName != "")
{
this->m_LoggerImpl->Log(LogLevel::TRC, "Found {0}, but connection names are ignored.", connectionName);
}
std::string outName;
std::string inName;
ParameterMapType newProperties;
......@@ -692,7 +748,11 @@ BlueprintImpl::MergeProperties(const PropertyTreeType & pt)
}
else if (connectionKey == "Name")
{
this->m_LoggerImpl->Log(LogLevel::WRN, "Connections with key 'Name' are ignored.");
if (connectionName != "")
{
this->m_LoggerImpl->Log(LogLevel::WRN, "Connection Name '{}' is overridden by '{}'", connectionName, elm.second.data());
}
connectionName = elm.second.data();
continue;
}
else
......@@ -704,10 +764,10 @@ BlueprintImpl::MergeProperties(const PropertyTreeType & pt)
}
// Does the blueprint have a connection that already exists?
if (this->ConnectionExists(outName, inName))
if (this->ConnectionExists(outName, inName, connectionName))
{
// Connection exists, check if properties can be merged
auto ownProperties = this->GetConnection(outName, inName);
auto ownProperties = this->GetConnection(outName, inName, connectionName);
for (auto const & othersEntry : newProperties)
{
......@@ -720,8 +780,8 @@ BlueprintImpl::MergeProperties(const PropertyTreeType & pt)
if (ownValues.size() != otherValues.size())
{
// No, based on the number of values we see that it is different. Blueprints cannot be Composed
this->m_LoggerImpl->Log(LogLevel::ERR, "Merging blueprints failed : Component properties cannot be redefined");
throw std::invalid_argument("Merging blueprints failed: Component properties cannot be redefined");
this->m_LoggerImpl->Log(LogLevel::ERR, "Merging blueprints failed : Connection properties cannot be redefined");
throw std::invalid_argument("Merging blueprints failed: Connection properties cannot be redefined");
}
else
{
......@@ -732,8 +792,8 @@ BlueprintImpl::MergeProperties(const PropertyTreeType & pt)
if (*otherValue != *ownValue)
{
// No, at least one value is different. Blueprints cannot be Composed
this->m_LoggerImpl->Log(LogLevel::ERR, "Merging blueprints failed : Component properties cannot be redefined");
throw std::invalid_argument("Merging blueprints failed: Component properties cannot be redefined");
this->m_LoggerImpl->Log(LogLevel::ERR, "Merging blueprints failed : Connection properties cannot be redefined");
throw std::invalid_argument("Merging blueprints failed: Connection properties cannot be redefined");
}
}
}
......@@ -743,14 +803,14 @@ BlueprintImpl::MergeProperties(const PropertyTreeType & pt)
// Property key doesn't exist yet, add entry to this component
//auto ownProperties = this->GetConnection(incomingName, componentName);
ownProperties[othersEntry.first] = othersEntry.second;
this->SetConnection(outName, inName, ownProperties);
this->SetConnection(outName, inName, ownProperties, connectionName);
}
}
}
else
{
// Create Component copying properties of other
this->SetConnection(outName, inName, newProperties);
this->SetConnection(outName, inName, newProperties, connectionName);
}
}
}
......
......@@ -60,6 +60,10 @@ public:
typedef Blueprint::ParameterMapType ParameterMapType;
typedef Blueprint::ComponentNameType ComponentNameType;
typedef Blueprint::ComponentNamesType ComponentNamesType;
typedef Blueprint::ConnectionNameType ConnectionNameType;
typedef Blueprint::ConnectionNamesType ConnectionNamesType;
// Component parameter map that sits on a node in the graph
// and holds component configuration settings
......@@ -74,17 +78,18 @@ public:
// and holds component connection configuration settings
struct ConnectionPropertyType
{
ConnectionPropertyType( ParameterMapType parameterMap = {} ) : parameterMap( parameterMap ) {}
ConnectionPropertyType( ConnectionNameType name = "", ParameterMapType parameterMap = {} ) :name( name ), parameterMap( parameterMap ) {}
ConnectionNameType name;
ParameterMapType parameterMap;
};
typedef boost::labeled_graph<
boost::adjacency_list<
boost::vecS,
boost::vecS,
boost::bidirectionalS,
ComponentPropertyType,
ConnectionPropertyType
boost::multisetS, //OutEdgeList: allow multiple parallel connections
boost::vecS, // VertexList,
boost::bidirectionalS, //Directed: directed graph, but give access to traverse in both directions
ComponentPropertyType, //VertexProperties
ConnectionPropertyType //GraphProperties
>,
ComponentNameType > GraphType;
......@@ -116,13 +121,15 @@ public:
// Returns a vector of the all Component names in the graph.
ComponentNamesType GetComponentNames( void ) const;
bool SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap );
// SetConnection with parameters parameterMap between components upstream and downstream.
// The connection name is to distinguish parallel connections. For single connections name = "" is typical.
bool SetConnection( ComponentNameType upstream, ComponentNameType downstream, ParameterMapType parameterMap, ConnectionNameType name );
ParameterMapType GetConnection( ComponentNameType upstream, ComponentNameType downstream ) const;
ParameterMapType GetConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name ) const;
bool DeleteConnection( ComponentNameType upstream, ComponentNameType downstream );
bool DeleteConnection( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name );
bool ConnectionExists( ComponentNameType upstream, ComponentNameType downstream ) const;
bool ConnectionExists( ComponentNameType upstream, ComponentNameType downstream, ConnectionNameType name ) const;
bool ComposeWith( const BlueprintImpl & other );
......@@ -132,6 +139,9 @@ public:
// Returns a vector of the Component names at the outgoing direction
ComponentNamesType GetOutputNames( const ComponentNameType name ) const;
// Returns a vector of the connection names between upstream and downstream
ComponentNamesType GetConnectionNames(const ComponentNameType upstream, const ComponentNameType downstream) const;
ComponentNamesType GetUpdateOrder() const;
void Write( const std::string filename );
......@@ -148,8 +158,6 @@ private:
using PathType = boost::filesystem::path;
using PathsType = std::list<PathType>;
ConnectionIndexType GetConnectionIndex(ComponentNameType upsteam, ComponentNameType downstream) const;
PropertyTreeType ReadPropertyTree(const PathType & filename);
PathsType FindIncludes(const PropertyTreeType &);
......
......@@ -186,53 +186,9 @@ TEST_F( BlueprintTest, Compose )
EXPECT_EQ( 0, baseBlueprint->GetComponent( "Component1" ).count( "InternalComputationValueType" ) );
}
//TEST_F( BlueprintTest, WriteBlueprint )
//{
// std::unique_ptr< BlueprintImpl > blueprint;
// EXPECT_NO_THROW( blueprint = std::unique_ptr< BlueprintImpl >( new BlueprintImpl() ) );
//
// // create some made up configuration to show graphviz output
// ParameterMapType component0Parameters;
// component0Parameters[ "NameOfClass" ] = { "MyMetric" };
// component0Parameters[ "Dimensionality" ] = { "3" };
// component0Parameters[ "Kernel" ] = { "5", "5", "5" };
// blueprint->SetComponent( "Metric", component0Parameters );
//
// ParameterMapType component1Parameters;
// component1Parameters[ "NameOfClass" ] = { "MyFiniteDifferenceCalculator" };
// component1Parameters[ "Delta" ] = { "0.01" };
// blueprint->SetComponent( "MetricGradient", component1Parameters );
//
// ParameterMapType component2Parameters;
// component2Parameters[ "NameOfClass" ] = { "MyOptimizer" };
// blueprint->SetComponent( "Optimizer", component2Parameters );
//
// ParameterMapType component3Parameters;
// component3Parameters[ "NameOfClass" ] = { "MyTransform" };
// blueprint->SetComponent( "Transform", component3Parameters );
//
// blueprint->SetConnection( "Metric", "MetricGradient", parameterMap );
// blueprint->SetConnection( "MetricGradient", "Optimizer", parameterMap );
//
// ParameterMapType connection0Parameters;
// // Example use case: The connection between the metric and optimizer should
// // only be by "MetricValue", not by "MetricDerivative" as well. Since we want
// // to redirect the "MetricDerivative" through the MetricGradient component,
// // we need to specify "NameOfInterface" otherwise there is an ambiguity in
// // which "MetricDerivative" to connect to the optimizer.
//
// connection0Parameters[ "NameOfInterface" ] = { "MetricValue" };
// blueprint->SetConnection( "Metric", "Optimizer", connection0Parameters );
//
// blueprint->SetConnection( "MetricGradient", "Optimizer", parameterMap );
// blueprint->SetConnection( "Optimizer", "Transform", parameterMap );
// blueprint->SetConnection( "Transform", "Metric", parameterMap );
//
// EXPECT_NO_THROW( blueprint->Write( "blueprint.dot" ) );
//}
TEST_F(BlueprintTest, ReadXML)
TEST_F(BlueprintTest, ReadXMLWriteDot)
{
auto blueprint = Blueprint::New();
......@@ -240,10 +196,39 @@ TEST_F(BlueprintTest, ReadXML)
blueprint->Write(this->dataManager->GetOutputFile("configurationReaderTest_itkv4_SVF_ANTsCC.xml.dot"));
}
TEST_F(BlueprintTest, ReadJson)
TEST_F(BlueprintTest, ReadJsonWriteDot)
{
auto blueprint = Blueprint::New();
EXPECT_NO_THROW(blueprint->MergeFromFile(this->dataManager->GetConfigurationFile("itkv4_SVF_ANTsCC.json")));
blueprint->Write(this->dataManager->GetOutputFile("configurationReaderTest_itkv4_SVF_ANTsCC.json.dot"));
}
TEST_F(BlueprintTest, ParallelConnections) //#150: Let Blueprint handle two connections between the same components
{
auto blueprint = Blueprint::New();
blueprint->SetComponent("ComponentA", { });
blueprint->SetComponent("ComponentB", { });
ParameterMapType firstConnection = {{"NameOfInterface", {"FirstInterface"}}};
blueprint->SetConnection("ComponentA", "ComponentB", firstConnection, "FirstConnection");
ParameterMapType secondConnection = {{"NameOfInterface", {"SecondInterface"}}};
blueprint->SetConnection("ComponentA", "ComponentB", secondConnection, "SecondConnection");
auto connection1 = blueprint->GetConnection("ComponentA", "ComponentB","FirstConnection");
EXPECT_EQ(firstConnection["NameOfInterface"][0], connection1["NameOfInterface"][0]);
auto connection2 = blueprint->GetConnection("ComponentA", "ComponentB","SecondConnection");
EXPECT_EQ(secondConnection["NameOfInterface"][0], connection2["NameOfInterface"][0]);
blueprint->DeleteConnection("ComponentA", "ComponentB", "FirstConnection");
EXPECT_FALSE(blueprint->ConnectionExists("ComponentA", "ComponentB", "FirstConnection"));
EXPECT_TRUE(blueprint->ConnectionExists("ComponentA", "ComponentB", "SecondConnection"));
blueprint->DeleteConnection("ComponentA", "ComponentB", "SecondConnection");
EXPECT_FALSE(blueprint->ConnectionExists("ComponentA", "ComponentB", "SecondConnection"));
}
TEST_F(BlueprintTest, ReadParallelConnections) //#150: Let Blueprint reader handle two connections between the same components
{
auto blueprint = Blueprint::New();
EXPECT_NO_THROW(blueprint->MergeFromFile(this->dataManager->GetConfigurationFile("ReadParallelConnections.json")));
}
\ No newline at end of file
......@@ -186,48 +186,50 @@ NetworkBuilder< ComponentList >::ApplyConnectionConfiguration()
{
for( auto const & acceptingComponentName : this->m_Blueprint.GetOutputNames( providingComponentName ) )
{