Skip to content

Commit

Permalink
runtime: Support components with multiple inputs
Browse files Browse the repository at this point in the history
  • Loading branch information
tobifalk committed Apr 22, 2021
1 parent d42419e commit c867eab
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 77 deletions.
29 changes: 18 additions & 11 deletions engine/src/simulation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,19 +478,21 @@ StateId SimulationMachine::Connect::impl(SimulationContext& ctx) {
*/
auto new_component = [&ctx](cloe::Vehicle& v,
const cloe::ComponentConf& c) -> std::shared_ptr<cloe::Component> {
// Create a copy of the component factory prototype and initialize it with the default stack arguments.
auto f = c.factory->clone();
auto name = c.name.value_or(c.binding);
for (auto d : ctx.config.get_component_defaults(name, f->name())) {
f->from_conf(d.args);
}
std::shared_ptr<cloe::Component> from;
if (c.from) {
if (v.has(*c.from)) {
from = v.get<cloe::Component>(*c.from);
} else {
// Get input components, if applicable.
std::vector<std::shared_ptr<cloe::Component>> from;
for (const auto& from_comp_name : c.from) {
if (!v.has(from_comp_name)) {
return nullptr;
}
from.push_back(v.get<cloe::Component>(from_comp_name));
}
// Create the new component.
auto x = f->make(c.args, std::move(from));
ctx.now_initializing = x.get();

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

// We now have a component that has not been configured, and this
// can only be the case if the dependency is not found.
assert(kv.second.from);
throw cloe::ModelError{
"cannot configure component '{}': cannot resolve dependency '{}'",
kv.first,
*kv.second.from,
};
assert(kv.second.from.size() > 0);
for (const auto& from_comp_name : kv.second.from) {
if (x->has(from_comp_name)) {
continue;
}
throw cloe::ModelError{
"cannot configure component '{}': cannot resolve dependency '{}'",
kv.first,
from_comp_name,
};
}
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion engine/src/stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@ ControllerSchema::ControllerSchema() {
ComponentSchema::ComponentSchema() {
this->set_transform_schema([](fable::schema::Struct&& s) -> fable::schema::Box {
s.set_property("name", id_prototype("globally unique identifier for component"));
s.set_property("from", make_prototype<std::string>("component input for binding"));
s.set_property("from",
fable::schema::Variant{
make_prototype<std::string>("component input for binding"),
make_prototype<std::vector<std::string>>("component inputs for binding"),
});
return s;
});
}
Expand Down
20 changes: 18 additions & 2 deletions engine/src/stack.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <cloe/simulator.hpp> // for SimulatorFactory
#include <cloe/trigger.hpp> // for Source
#include <cloe/utility/command.hpp> // for Command
#include <fable/schema/custom.hpp> // for CustomDeserializer
#include <fable/schema/factory.hpp> // for Factory

#include "plugin.hpp" // for Plugin
Expand Down Expand Up @@ -616,7 +617,7 @@ struct FromSimulator : public Confable {
struct ComponentConf : public Confable {
const std::string binding;
boost::optional<std::string> name;
boost::optional<std::string> from;
std::vector<std::string> from;
std::shared_ptr<ComponentFactory> factory;
Conf args;

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

public: // Confable Overrides
CONFABLE_SCHEMA(ComponentConf) {
// clang-format off
using namespace schema; // NOLINT(build/namespaces)
return Struct{
{"binding", make_const_str(binding, "name of binding").require()},
{"name", make_schema(&name, id_prototype(), "globally unique identifier for component")},
{"from", make_schema(&from, "component input for binding")},
{"from", Variant{
CustomDeserializer(
make_prototype<std::string>("component input for binding"),
[this](CustomDeserializer*, const Conf& c) {
this->from.push_back(c.get<std::string>());
}
),
CustomDeserializer(
make_prototype<std::vector<std::string>>("component inputs for binding"),
[this](CustomDeserializer*, const Conf& c) {
this->from = c.get<std::vector<std::string>>();
}
),
}},
{"args", make_schema(&args, factory->schema(), "factory-specific args")},
};
// clang-format on
}
};

Expand Down
142 changes: 105 additions & 37 deletions engine/src/stack_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <vector>

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

fable::assert_from_conf(s, R"(
{
"version": "4"
}
)");
fable::assert_from_conf(s, R"({
"version": "4"
})");

Json expect = R"({
"engine": {
Expand Down Expand Up @@ -120,19 +119,17 @@ TEST(cloe_stack, serialization_of_empty_stack) {
TEST(cloe_stack, serialization_with_logging) {
Stack s;

assert_from_conf(s, R"(
{
"version": "4",
"defaults": {
"simulators": [
{ "binding": "vtd", "args": { "label_vehicle": "symbol" } }
]
},
"logging": [
{ "name": "*", "level": "info" }
assert_from_conf(s, R"({
"version": "4",
"defaults": {
"simulators": [
{ "binding": "vtd", "args": { "label_vehicle": "symbol" } }
]
}
)");
},
"logging": [
{ "name": "*", "level": "info" }
]
})");

Json expect = R"({
"engine": {
Expand Down Expand Up @@ -249,25 +246,96 @@ DEFINE_COMPONENT_FACTORY(DummySensorFactory, DummySensorConf, "dummy_object_sens
DEFINE_COMPONENT_FACTORY_MAKE(DummySensorFactory, DummySensor, cloe::ObjectSensor)

TEST(cloe_stack, deserialization_of_component) {
{
std::shared_ptr<DummySensorFactory> cf = std::make_shared<DummySensorFactory>();
ComponentConf cc = ComponentConf("dummy_sensor", cf);
// Create a sensor component from the given configuration.
fable::assert_from_conf(cc, R"(
{
"binding": "dummy_sensor",
"name": "my_dummy_sensor",
"from": "some_obj_sensor",
"args" : {
"freq" : 9
}
}
)");
// In production code, "some_obj_sensor" would be fetched from a list of all
// available sensors. Skip this step here.
std::shared_ptr<cloe::Component> from = std::shared_ptr<cloe::NopObjectSensor>();
auto d = std::dynamic_pointer_cast<DummySensor>(
std::shared_ptr<cloe::Component>(std::move(cf->make(cc.args, from))));
ASSERT_EQ(d->get_freq(), 9);
// Create a sensor component from the given configuration.
std::shared_ptr<DummySensorFactory> cf = std::make_shared<DummySensorFactory>();
ComponentConf cc = ComponentConf("dummy_sensor", cf);
fable::assert_from_conf(cc, R"({
"binding": "dummy_sensor",
"name": "my_dummy_sensor",
"from": "some_obj_sensor",
"args" : {
"freq" : 9
}
})");

// In production code, "some_obj_sensor" would be fetched from a list of all
// available sensors. Skip this step here.
std::vector<std::shared_ptr<cloe::Component>> from = {std::shared_ptr<cloe::NopObjectSensor>()};
auto d = std::dynamic_pointer_cast<DummySensor>(
std::shared_ptr<cloe::Component>(std::move(cf->make(cc.args, from))));
ASSERT_EQ(d->get_freq(), 9);
}

class FusionSensor : public NopObjectSensor {
public:
FusionSensor(const std::string& name, const DummySensorConf& conf,
std::vector<std::shared_ptr<ObjectSensor>> obj_sensors,
std::shared_ptr<EgoSensor> ego_sensor)
: NopObjectSensor(), config_(conf), obj_sensors_(obj_sensors), ego_sensor_(ego_sensor) {}

virtual ~FusionSensor() noexcept = default;

uint64_t get_freq() const { return config_.freq; }

private:
DummySensorConf config_;
std::vector<std::shared_ptr<ObjectSensor>> obj_sensors_;
std::shared_ptr<EgoSensor> ego_sensor_;
};

DEFINE_COMPONENT_FACTORY(FusionSensorFactory, DummySensorConf, "fusion_object_sensor",
"test component config")

std::unique_ptr<::cloe::Component> FusionSensorFactory::make(
const ::cloe::Conf& c, std::vector<std::shared_ptr<cloe::Component>> comp_src) const {
decltype(config_) conf{config_};
if (!c->is_null()) {
conf.from_conf(c);
}
std::vector<std::shared_ptr<ObjectSensor>> obj_sensors;
std::vector<std::shared_ptr<EgoSensor>> ego_sensors;
for (auto& comp : comp_src) {
auto obj_s = std::dynamic_pointer_cast<ObjectSensor>(comp);
if (obj_s != nullptr) {
obj_sensors.push_back(obj_s);
continue;
}
auto ego_s = std::dynamic_pointer_cast<EgoSensor>(comp);
if (ego_s != nullptr) {
ego_sensors.push_back(ego_s);
continue;
}
throw Error("FusionSensorFactory: Source component type not supported: from {}", comp->name());
}
if (ego_sensors.size() != 1) {
throw Error("FusionSensorFactory: {}: Require exactly one ego sensor.", this->name());
}
return std::make_unique<FusionSensor>(this->name(), conf, obj_sensors, ego_sensors.front());
}

TEST(cloe_stack, deserialization_of_fusion_component) {
// Create a sensor component from the given configuration.
std::shared_ptr<FusionSensorFactory> cf = std::make_shared<FusionSensorFactory>();
ComponentConf cc = ComponentConf("fusion_object_sensor", cf);
fable::assert_from_conf(cc, R"({
"binding": "fusion_object_sensor",
"name": "my_fusion_sensor",
"from": [
"ego_sensor0",
"obj_sensor1",
"obj_sensor2"
],
"args" : {
"freq" : 77
}
})");

// In production code, a component list containing "ego_sensor0", ... would
// be generated. Skip this step here.
std::vector<std::shared_ptr<cloe::Component>> sensor_subset = {
std::make_shared<cloe::NopEgoSensor>(), std::make_shared<cloe::NopObjectSensor>(),
std::make_shared<cloe::NopObjectSensor>()};
auto f = std::dynamic_pointer_cast<FusionSensor>(
std::shared_ptr<cloe::Component>(std::move(cf->make(cc.args, sensor_subset))));
ASSERT_EQ(f->get_freq(), 77);
}
55 changes: 29 additions & 26 deletions runtime/include/cloe/component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,40 +53,42 @@
* The DEFINE_COMPONENT_FACTORY_MAKE macro can also be used to use the default
* implementation.
*/
#define DEFINE_COMPONENT_FACTORY(xFactoryType, xConfigType, xName, xDescription) \
class xFactoryType : public ::cloe::ComponentFactory { \
public: \
xFactoryType() : ComponentFactory(xName, xDescription) {} \
std::unique_ptr<::cloe::ComponentFactory> clone() const override { \
return std::make_unique<std::decay<decltype(*this)>::type>(*this); \
} \
std::unique_ptr<::cloe::Component> make(const ::cloe::Conf&, \
std::shared_ptr<::cloe::Component>) const override; \
\
protected: \
::cloe::Schema schema_impl() override { return config_.schema(); } \
\
private: \
xConfigType config_; \
#define DEFINE_COMPONENT_FACTORY(xFactoryType, xConfigType, xName, xDescription) \
class xFactoryType : public ::cloe::ComponentFactory { \
public: \
xFactoryType() : ComponentFactory(xName, xDescription) {} \
std::unique_ptr<::cloe::ComponentFactory> clone() const override { \
return std::make_unique<std::decay<decltype(*this)>::type>(*this); \
} \
std::unique_ptr<::cloe::Component> make( \
const ::cloe::Conf&, std::vector<std::shared_ptr<::cloe::Component>>) const override; \
\
protected: \
::cloe::Schema schema_impl() override { return config_.schema(); } \
\
private: \
xConfigType config_; \
};

/**
* This macro defines the xFactoryType::make method.
* This macro defines the xFactoryType::make method for components with exactly
* one input component.
*
* For this to work, the xComponentType must have a constructor with the
* following signature (see DEFINE_COMPONENT_FACTORY macro):
*
* xComponentType(const std::string&, const xConfigType&, std::shared_ptr<xInputType>)
*/
#define DEFINE_COMPONENT_FACTORY_MAKE(xFactoryType, xComponentType, xInputType) \
std::unique_ptr<::cloe::Component> xFactoryType::make( \
const ::cloe::Conf& c, std::shared_ptr<::cloe::Component> comp) const { \
decltype(config_) conf{config_}; \
if (!c->is_null()) { \
conf.from_conf(c); \
} \
return std::make_unique<xComponentType>( \
this->name(), conf, std::dynamic_pointer_cast<xInputType>(std::move(comp))); \
#define DEFINE_COMPONENT_FACTORY_MAKE(xFactoryType, xComponentType, xInputType) \
std::unique_ptr<::cloe::Component> xFactoryType::make( \
const ::cloe::Conf& c, std::vector<std::shared_ptr<::cloe::Component>> comp) const { \
decltype(config_) conf{config_}; \
assert(comp.size() == 1); \
if (!c->is_null()) { \
conf.from_conf(c); \
} \
return std::make_unique<xComponentType>( \
this->name(), conf, std::dynamic_pointer_cast<xInputType>(std::move(comp.front()))); \
}

namespace cloe {
Expand Down Expand Up @@ -236,7 +238,8 @@ class ComponentFactory : public ModelFactory {
*
* - This method may throw Error.
*/
virtual std::unique_ptr<Component> make(const Conf& c, std::shared_ptr<Component>) const = 0;
virtual std::unique_ptr<Component> make(const Conf& c,
std::vector<std::shared_ptr<Component>>) const = 0;
};

} // namespace cloe
Expand Down

0 comments on commit c867eab

Please sign in to comment.