Skip to content

Commit c867eab

Browse files
committed
runtime: Support components with multiple inputs
1 parent d42419e commit c867eab

File tree

5 files changed

+175
-77
lines changed

5 files changed

+175
-77
lines changed

engine/src/simulation.cpp

+18-11
Original file line numberDiff line numberDiff line change
@@ -478,19 +478,21 @@ StateId SimulationMachine::Connect::impl(SimulationContext& ctx) {
478478
*/
479479
auto new_component = [&ctx](cloe::Vehicle& v,
480480
const cloe::ComponentConf& c) -> std::shared_ptr<cloe::Component> {
481+
// Create a copy of the component factory prototype and initialize it with the default stack arguments.
481482
auto f = c.factory->clone();
482483
auto name = c.name.value_or(c.binding);
483484
for (auto d : ctx.config.get_component_defaults(name, f->name())) {
484485
f->from_conf(d.args);
485486
}
486-
std::shared_ptr<cloe::Component> from;
487-
if (c.from) {
488-
if (v.has(*c.from)) {
489-
from = v.get<cloe::Component>(*c.from);
490-
} else {
487+
// Get input components, if applicable.
488+
std::vector<std::shared_ptr<cloe::Component>> from;
489+
for (const auto& from_comp_name : c.from) {
490+
if (!v.has(from_comp_name)) {
491491
return nullptr;
492492
}
493+
from.push_back(v.get<cloe::Component>(from_comp_name));
493494
}
495+
// Create the new component.
494496
auto x = f->make(c.args, std::move(from));
495497
ctx.now_initializing = x.get();
496498

@@ -578,12 +580,17 @@ StateId SimulationMachine::Connect::impl(SimulationContext& ctx) {
578580

579581
// We now have a component that has not been configured, and this
580582
// can only be the case if the dependency is not found.
581-
assert(kv.second.from);
582-
throw cloe::ModelError{
583-
"cannot configure component '{}': cannot resolve dependency '{}'",
584-
kv.first,
585-
*kv.second.from,
586-
};
583+
assert(kv.second.from.size() > 0);
584+
for (const auto& from_comp_name : kv.second.from) {
585+
if (x->has(from_comp_name)) {
586+
continue;
587+
}
588+
throw cloe::ModelError{
589+
"cannot configure component '{}': cannot resolve dependency '{}'",
590+
kv.first,
591+
from_comp_name,
592+
};
593+
}
587594
}
588595
}
589596
}

engine/src/stack.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@ ControllerSchema::ControllerSchema() {
119119
ComponentSchema::ComponentSchema() {
120120
this->set_transform_schema([](fable::schema::Struct&& s) -> fable::schema::Box {
121121
s.set_property("name", id_prototype("globally unique identifier for component"));
122-
s.set_property("from", make_prototype<std::string>("component input for binding"));
122+
s.set_property("from",
123+
fable::schema::Variant{
124+
make_prototype<std::string>("component input for binding"),
125+
make_prototype<std::vector<std::string>>("component inputs for binding"),
126+
});
123127
return s;
124128
});
125129
}

engine/src/stack.hpp

+18-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include <cloe/simulator.hpp> // for SimulatorFactory
4040
#include <cloe/trigger.hpp> // for Source
4141
#include <cloe/utility/command.hpp> // for Command
42+
#include <fable/schema/custom.hpp> // for CustomDeserializer
4243
#include <fable/schema/factory.hpp> // for Factory
4344

4445
#include "plugin.hpp" // for Plugin
@@ -616,7 +617,7 @@ struct FromSimulator : public Confable {
616617
struct ComponentConf : public Confable {
617618
const std::string binding;
618619
boost::optional<std::string> name;
619-
boost::optional<std::string> from;
620+
std::vector<std::string> from;
620621
std::shared_ptr<ComponentFactory> factory;
621622
Conf args;
622623

@@ -626,13 +627,28 @@ struct ComponentConf : public Confable {
626627

627628
public: // Confable Overrides
628629
CONFABLE_SCHEMA(ComponentConf) {
630+
// clang-format off
629631
using namespace schema; // NOLINT(build/namespaces)
630632
return Struct{
631633
{"binding", make_const_str(binding, "name of binding").require()},
632634
{"name", make_schema(&name, id_prototype(), "globally unique identifier for component")},
633-
{"from", make_schema(&from, "component input for binding")},
635+
{"from", Variant{
636+
CustomDeserializer(
637+
make_prototype<std::string>("component input for binding"),
638+
[this](CustomDeserializer*, const Conf& c) {
639+
this->from.push_back(c.get<std::string>());
640+
}
641+
),
642+
CustomDeserializer(
643+
make_prototype<std::vector<std::string>>("component inputs for binding"),
644+
[this](CustomDeserializer*, const Conf& c) {
645+
this->from = c.get<std::vector<std::string>>();
646+
}
647+
),
648+
}},
634649
{"args", make_schema(&args, factory->schema(), "factory-specific args")},
635650
};
651+
// clang-format on
636652
}
637653
};
638654

engine/src/stack_test.cpp

+105-37
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <vector>
2727

2828
#include <cloe/component.hpp> // for DEFINE_COMPONENT_FACTORY
29+
#include <cloe/component/ego_sensor.hpp> // for EgoSensor
2930
#include <cloe/component/object_sensor.hpp> // for ObjectSensor
3031
#include <cloe/core.hpp> // for Json
3132
#include <fable/utility/gtest.hpp> // for assert_from_conf
@@ -35,11 +36,9 @@ using namespace cloe; // NOLINT(build/namespaces)
3536
TEST(cloe_stack, serialization_of_empty_stack) {
3637
Stack s;
3738

38-
fable::assert_from_conf(s, R"(
39-
{
40-
"version": "4"
41-
}
42-
)");
39+
fable::assert_from_conf(s, R"({
40+
"version": "4"
41+
})");
4342

4443
Json expect = R"({
4544
"engine": {
@@ -120,19 +119,17 @@ TEST(cloe_stack, serialization_of_empty_stack) {
120119
TEST(cloe_stack, serialization_with_logging) {
121120
Stack s;
122121

123-
assert_from_conf(s, R"(
124-
{
125-
"version": "4",
126-
"defaults": {
127-
"simulators": [
128-
{ "binding": "vtd", "args": { "label_vehicle": "symbol" } }
129-
]
130-
},
131-
"logging": [
132-
{ "name": "*", "level": "info" }
122+
assert_from_conf(s, R"({
123+
"version": "4",
124+
"defaults": {
125+
"simulators": [
126+
{ "binding": "vtd", "args": { "label_vehicle": "symbol" } }
133127
]
134-
}
135-
)");
128+
},
129+
"logging": [
130+
{ "name": "*", "level": "info" }
131+
]
132+
})");
136133

137134
Json expect = R"({
138135
"engine": {
@@ -249,25 +246,96 @@ DEFINE_COMPONENT_FACTORY(DummySensorFactory, DummySensorConf, "dummy_object_sens
249246
DEFINE_COMPONENT_FACTORY_MAKE(DummySensorFactory, DummySensor, cloe::ObjectSensor)
250247

251248
TEST(cloe_stack, deserialization_of_component) {
252-
{
253-
std::shared_ptr<DummySensorFactory> cf = std::make_shared<DummySensorFactory>();
254-
ComponentConf cc = ComponentConf("dummy_sensor", cf);
255-
// Create a sensor component from the given configuration.
256-
fable::assert_from_conf(cc, R"(
257-
{
258-
"binding": "dummy_sensor",
259-
"name": "my_dummy_sensor",
260-
"from": "some_obj_sensor",
261-
"args" : {
262-
"freq" : 9
263-
}
264-
}
265-
)");
266-
// In production code, "some_obj_sensor" would be fetched from a list of all
267-
// available sensors. Skip this step here.
268-
std::shared_ptr<cloe::Component> from = std::shared_ptr<cloe::NopObjectSensor>();
269-
auto d = std::dynamic_pointer_cast<DummySensor>(
270-
std::shared_ptr<cloe::Component>(std::move(cf->make(cc.args, from))));
271-
ASSERT_EQ(d->get_freq(), 9);
249+
// Create a sensor component from the given configuration.
250+
std::shared_ptr<DummySensorFactory> cf = std::make_shared<DummySensorFactory>();
251+
ComponentConf cc = ComponentConf("dummy_sensor", cf);
252+
fable::assert_from_conf(cc, R"({
253+
"binding": "dummy_sensor",
254+
"name": "my_dummy_sensor",
255+
"from": "some_obj_sensor",
256+
"args" : {
257+
"freq" : 9
258+
}
259+
})");
260+
261+
// In production code, "some_obj_sensor" would be fetched from a list of all
262+
// available sensors. Skip this step here.
263+
std::vector<std::shared_ptr<cloe::Component>> from = {std::shared_ptr<cloe::NopObjectSensor>()};
264+
auto d = std::dynamic_pointer_cast<DummySensor>(
265+
std::shared_ptr<cloe::Component>(std::move(cf->make(cc.args, from))));
266+
ASSERT_EQ(d->get_freq(), 9);
267+
}
268+
269+
class FusionSensor : public NopObjectSensor {
270+
public:
271+
FusionSensor(const std::string& name, const DummySensorConf& conf,
272+
std::vector<std::shared_ptr<ObjectSensor>> obj_sensors,
273+
std::shared_ptr<EgoSensor> ego_sensor)
274+
: NopObjectSensor(), config_(conf), obj_sensors_(obj_sensors), ego_sensor_(ego_sensor) {}
275+
276+
virtual ~FusionSensor() noexcept = default;
277+
278+
uint64_t get_freq() const { return config_.freq; }
279+
280+
private:
281+
DummySensorConf config_;
282+
std::vector<std::shared_ptr<ObjectSensor>> obj_sensors_;
283+
std::shared_ptr<EgoSensor> ego_sensor_;
284+
};
285+
286+
DEFINE_COMPONENT_FACTORY(FusionSensorFactory, DummySensorConf, "fusion_object_sensor",
287+
"test component config")
288+
289+
std::unique_ptr<::cloe::Component> FusionSensorFactory::make(
290+
const ::cloe::Conf& c, std::vector<std::shared_ptr<cloe::Component>> comp_src) const {
291+
decltype(config_) conf{config_};
292+
if (!c->is_null()) {
293+
conf.from_conf(c);
272294
}
295+
std::vector<std::shared_ptr<ObjectSensor>> obj_sensors;
296+
std::vector<std::shared_ptr<EgoSensor>> ego_sensors;
297+
for (auto& comp : comp_src) {
298+
auto obj_s = std::dynamic_pointer_cast<ObjectSensor>(comp);
299+
if (obj_s != nullptr) {
300+
obj_sensors.push_back(obj_s);
301+
continue;
302+
}
303+
auto ego_s = std::dynamic_pointer_cast<EgoSensor>(comp);
304+
if (ego_s != nullptr) {
305+
ego_sensors.push_back(ego_s);
306+
continue;
307+
}
308+
throw Error("FusionSensorFactory: Source component type not supported: from {}", comp->name());
309+
}
310+
if (ego_sensors.size() != 1) {
311+
throw Error("FusionSensorFactory: {}: Require exactly one ego sensor.", this->name());
312+
}
313+
return std::make_unique<FusionSensor>(this->name(), conf, obj_sensors, ego_sensors.front());
314+
}
315+
316+
TEST(cloe_stack, deserialization_of_fusion_component) {
317+
// Create a sensor component from the given configuration.
318+
std::shared_ptr<FusionSensorFactory> cf = std::make_shared<FusionSensorFactory>();
319+
ComponentConf cc = ComponentConf("fusion_object_sensor", cf);
320+
fable::assert_from_conf(cc, R"({
321+
"binding": "fusion_object_sensor",
322+
"name": "my_fusion_sensor",
323+
"from": [
324+
"ego_sensor0",
325+
"obj_sensor1",
326+
"obj_sensor2"
327+
],
328+
"args" : {
329+
"freq" : 77
330+
}
331+
})");
332+
333+
// In production code, a component list containing "ego_sensor0", ... would
334+
// be generated. Skip this step here.
335+
std::vector<std::shared_ptr<cloe::Component>> sensor_subset = {
336+
std::make_shared<cloe::NopEgoSensor>(), std::make_shared<cloe::NopObjectSensor>(),
337+
std::make_shared<cloe::NopObjectSensor>()};
338+
auto f = std::dynamic_pointer_cast<FusionSensor>(
339+
std::shared_ptr<cloe::Component>(std::move(cf->make(cc.args, sensor_subset))));
340+
ASSERT_EQ(f->get_freq(), 77);
273341
}

runtime/include/cloe/component.hpp

+29-26
Original file line numberDiff line numberDiff line change
@@ -53,40 +53,42 @@
5353
* The DEFINE_COMPONENT_FACTORY_MAKE macro can also be used to use the default
5454
* implementation.
5555
*/
56-
#define DEFINE_COMPONENT_FACTORY(xFactoryType, xConfigType, xName, xDescription) \
57-
class xFactoryType : public ::cloe::ComponentFactory { \
58-
public: \
59-
xFactoryType() : ComponentFactory(xName, xDescription) {} \
60-
std::unique_ptr<::cloe::ComponentFactory> clone() const override { \
61-
return std::make_unique<std::decay<decltype(*this)>::type>(*this); \
62-
} \
63-
std::unique_ptr<::cloe::Component> make(const ::cloe::Conf&, \
64-
std::shared_ptr<::cloe::Component>) const override; \
65-
\
66-
protected: \
67-
::cloe::Schema schema_impl() override { return config_.schema(); } \
68-
\
69-
private: \
70-
xConfigType config_; \
56+
#define DEFINE_COMPONENT_FACTORY(xFactoryType, xConfigType, xName, xDescription) \
57+
class xFactoryType : public ::cloe::ComponentFactory { \
58+
public: \
59+
xFactoryType() : ComponentFactory(xName, xDescription) {} \
60+
std::unique_ptr<::cloe::ComponentFactory> clone() const override { \
61+
return std::make_unique<std::decay<decltype(*this)>::type>(*this); \
62+
} \
63+
std::unique_ptr<::cloe::Component> make( \
64+
const ::cloe::Conf&, std::vector<std::shared_ptr<::cloe::Component>>) const override; \
65+
\
66+
protected: \
67+
::cloe::Schema schema_impl() override { return config_.schema(); } \
68+
\
69+
private: \
70+
xConfigType config_; \
7171
};
7272

7373
/**
74-
* This macro defines the xFactoryType::make method.
74+
* This macro defines the xFactoryType::make method for components with exactly
75+
* one input component.
7576
*
7677
* For this to work, the xComponentType must have a constructor with the
7778
* following signature (see DEFINE_COMPONENT_FACTORY macro):
7879
*
7980
* xComponentType(const std::string&, const xConfigType&, std::shared_ptr<xInputType>)
8081
*/
81-
#define DEFINE_COMPONENT_FACTORY_MAKE(xFactoryType, xComponentType, xInputType) \
82-
std::unique_ptr<::cloe::Component> xFactoryType::make( \
83-
const ::cloe::Conf& c, std::shared_ptr<::cloe::Component> comp) const { \
84-
decltype(config_) conf{config_}; \
85-
if (!c->is_null()) { \
86-
conf.from_conf(c); \
87-
} \
88-
return std::make_unique<xComponentType>( \
89-
this->name(), conf, std::dynamic_pointer_cast<xInputType>(std::move(comp))); \
82+
#define DEFINE_COMPONENT_FACTORY_MAKE(xFactoryType, xComponentType, xInputType) \
83+
std::unique_ptr<::cloe::Component> xFactoryType::make( \
84+
const ::cloe::Conf& c, std::vector<std::shared_ptr<::cloe::Component>> comp) const { \
85+
decltype(config_) conf{config_}; \
86+
assert(comp.size() == 1); \
87+
if (!c->is_null()) { \
88+
conf.from_conf(c); \
89+
} \
90+
return std::make_unique<xComponentType>( \
91+
this->name(), conf, std::dynamic_pointer_cast<xInputType>(std::move(comp.front()))); \
9092
}
9193

9294
namespace cloe {
@@ -236,7 +238,8 @@ class ComponentFactory : public ModelFactory {
236238
*
237239
* - This method may throw Error.
238240
*/
239-
virtual std::unique_ptr<Component> make(const Conf& c, std::shared_ptr<Component>) const = 0;
241+
virtual std::unique_ptr<Component> make(const Conf& c,
242+
std::vector<std::shared_ptr<Component>>) const = 0;
240243
};
241244

242245
} // namespace cloe

0 commit comments

Comments
 (0)