Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/assign virtual configs #13

Merged
merged 3 commits into from
Apr 27, 2024
Merged
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
30 changes: 30 additions & 0 deletions config_utilities/include/config_utilities/factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,33 @@ struct ConfigWrapperImpl : public ConfigWrapper {
};
};

// Registry for config names based on derived types in the factory.
template <typename BaseT, typename ConfigT>
class ConfigTypeRegistry {
public:
static void setTypeName(const std::string& type) {
// NOTE(lschmid): This is not forbidden behavior, but is not recommended so for now simply warn the user.
std::string& type_ = instance().type_;
if (!type_.empty() && type_ != type) {
Logger::logInfo("Overwriting type name for config '" + typeName<ConfigT>() + "' for base module '" +
typeName<BaseT>() + "' from '" + instance().type_ + "' to '" + type +
"'. Defining different type identifiers for the same derived module is not recommended.");
}
type_ = type;
}
static std::string getType() { return instance().type_; }

private:
static ConfigTypeRegistry& instance() {
static ConfigTypeRegistry instance_;
return instance_;
}

ConfigTypeRegistry() = default;

std::string type_;
};

// Definitions of the Factories.
// Factory to create configs.
template <class BaseT>
Expand All @@ -237,6 +264,9 @@ struct ConfigFactory {
FactoryMethod method = [type]() { return new ConfigWrapperImpl<DerivedConfigT>(type); };
// If the config is already registered, e.g. from different constructor args no warning needs to be printed.
ModuleMap::addEntry(type, method, "");

// Register the type name for the config.
ConfigTypeRegistry<BaseT, DerivedConfigT>::setTypeName(type);
}

// Create the config.
Expand Down
55 changes: 41 additions & 14 deletions config_utilities/include/config_utilities/virtual_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,9 @@ template <class BaseT>
class VirtualConfig {
public:
VirtualConfig() = default;
~VirtualConfig() = default;

/**
* @brief Setup a virtual config from a manually specified config struct
* @tparam ConfigT Config type corresponding to the approriate config struct registered under type for BaseT
* @param conf Config instance to use
* @param type Corresponding registration type for the object factory for BaseT
*/
template <typename ConfigT>
VirtualConfig(const ConfigT& config, const std::string& type) {
auto wrapper = std::make_unique<internal::ConfigWrapperImpl<ConfigT>>(type);
wrapper->config = config;
config_ = std::move(wrapper);
}

// Copy operators.
// Copy and assignment operators.
VirtualConfig(const VirtualConfig& other) {
if (other.config_) {
config_ = other.config_->clone();
Expand All @@ -97,6 +85,44 @@ class VirtualConfig {
return *this;
}

/**
* @brief Assign a config to this virtual config. This will check that the config being assigned is registered for a
* module inheritin from the base class of this virtual config, and will use the registered type-string as type.
* NOTE: If the same config is registered with different names for different constructor arguments, config assignments
* may fail to retrieve the correct name.
* @tparam ConfigT The type of the config to assign.
* @param config The config to assign.
* @returns True if the config was set successfully, false otherwise.
*/
template <typename ConfigT>
bool set(const ConfigT& config) {
const std::string type = internal::ConfigTypeRegistry<BaseT, ConfigT>::getType();
if (type.empty()) {
// No type defined for the config.
internal::Logger::logError("No module for config '" + internal::typeName<ConfigT>() +
"' is registered to the factory for '" + internal::typeName<BaseT>() +
"' to set virtual config.");
return false;
}

// Assign the config.
auto wrapper = std::make_unique<internal::ConfigWrapperImpl<ConfigT>>(type);
wrapper->config = config;
config_ = std::move(wrapper);
return true;
}

template <typename ConfigT>
explicit VirtualConfig(const ConfigT& config) {
set(config);
}

template <typename ConfigT>
VirtualConfig& operator=(const ConfigT& config) {
set(config);
return *this;
}

/**
* @brief Check whether the config is populated with a config.
*
Expand All @@ -108,6 +134,7 @@ class VirtualConfig {
/**
* @brief Specify whether this config is optional. If it is optional, the config not being set is not an error.
* Otherwise the config must be set to be considered valid. By default virtual configs are not optional.
* @param optional Turn optional on (true) or off (false).
*/
void setOptional(bool optional = true) { optional_ = optional; }

Expand Down
1 change: 1 addition & 0 deletions config_utilities/test/tests/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ TEST(Factory, printRegistryInfo) {
},
config::test::Base2(): {
'Derived2' (config::test::Derived2),
'Derived2A' (config::test::Derived2A),
},
config::test::ProcessorBase(): {
'AddString' (config::test::AddString),
Expand Down
90 changes: 83 additions & 7 deletions config_utilities/test/tests/virtual_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,12 @@

namespace config::test {

class Base2 {
public:
struct Base2 {
virtual std::string name() const = 0;
virtual ~Base2() = default;
};

class Derived2 : public Base2 {
public:
struct Derived2 : public Base2 {
struct Config {
float f = 0.f;
std::string s = "test string";
Expand All @@ -66,15 +64,42 @@ class Derived2 : public Base2 {
};

void declare_config(Derived2::Config& config) {
// Declare the config using the config utilities.
config::name("Derived2");
config::field(config.f, "f", "m/s");
config::field(config.s, "s");
config::check(config.f, config::CheckMode::GE, 0.f, "f");
}

class ObjectWithBase {
public:
struct Derived2A : public Base2 {
struct Config {
int i = 0;
};
explicit Derived2A(const Config& config) : config_(config) {}
std::string name() const override { return "Derived2A"; }
const Config config_;
inline static const auto registration_ =
config::RegistrationWithConfig<Base2, Derived2A, Derived2A::Config>("Derived2A");
};

void declare_config(Derived2A::Config& config) {
config::name("Derived2A");
config::field(config.i, "i");
}

struct NotDerivedFromBase2 {
struct Config {
bool b = false;
VirtualConfig<Base2> base_config{Derived2::Config()};
};
};

void declare_config(NotDerivedFromBase2::Config& config) {
config::name("NotDerivedFromBase2");
config::field(config.b, "b");
config::field(config.base_config, "base_config");
}

struct ObjectWithBase {
struct Config {
double d = 0.0;
VirtualConfig<Base2> base_config;
Expand Down Expand Up @@ -148,6 +173,57 @@ TEST(VirtualConfig, copyMove) {
EXPECT_EQ(config5.getType(), "Derived2");
}

TEST(VirtualConfig, assignConfig) {
Derived2::Config derived_config;
derived_config.f = -1.f;
derived_config.s = "abcdef";

// Set a virtual config to a specified config.
VirtualConfig<Base2> config;
EXPECT_FALSE(config.isSet());
config.set(derived_config);
EXPECT_TRUE(config.isSet());
EXPECT_EQ(config.getType(), "Derived2");
auto derived = config.create();
EXPECT_TRUE(derived);
EXPECT_EQ(derived->name(), "Derived2");
const auto& realized_config = dynamic_cast<Derived2*>(derived.get())->config_;
EXPECT_EQ(realized_config.f, -1.f);
EXPECT_EQ(realized_config.s, "abcdef");

// Operator= should overwrite the config.
Derived2A::Config derived_config_a;
derived_config_a.i = 1;
config = derived_config_a;
EXPECT_TRUE(config.isSet());
EXPECT_EQ(config.getType(), "Derived2A");
derived = config.create();
EXPECT_TRUE(derived);
EXPECT_EQ(derived->name(), "Derived2A");
const auto& realized_config_a = dynamic_cast<Derived2A*>(derived.get())->config_;
EXPECT_EQ(realized_config_a.i, 1);

// Constructor and list initialization.
VirtualConfig<Base2> config2(derived_config);
EXPECT_TRUE(config2.isSet());
EXPECT_EQ(config2.getType(), "Derived2");

NotDerivedFromBase2::Config not_derived_config;
EXPECT_TRUE(not_derived_config.base_config.isSet());
EXPECT_EQ(not_derived_config.base_config.getType(), "Derived2");

// Fail to set a config that is not registered.
auto logger = TestLogger::create();
VirtualConfig<Base2> config3;
const bool success = config3.set(not_derived_config);
EXPECT_FALSE(success);
EXPECT_FALSE(config3.isSet());
EXPECT_EQ(logger->numMessages(), 1);
EXPECT_EQ(logger->messages()[0].second,
"No module for config 'config::test::NotDerivedFromBase2::Config' is registered to the factory for "
"'config::test::Base2' to set virtual config.");
}

TEST(VirtualConfig, create) {
VirtualConfig<Base2> config;
std::unique_ptr<Base2> object = config.create();
Expand Down