blob: e20f85451455f0a3a4af5954d761f04bf89dda7a [file]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MPACT_SIM_GENERIC_CONFIG_H_
#define MPACT_SIM_GENERIC_CONFIG_H_
#include <cstdint>
#include <functional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "absl/status/status.h"
#include "mpact/sim/generic/variant_helper.h"
#include "mpact/sim/proto/component_data.pb.h"
// This file defines a configuration type that is intended to be used in
// a simulator to access values that are specified at run time, such as the
// depth of a fifo, etc. The idea is that configuration entries are instantiated
// in software components, optionally with a default value. Prior to the start
// of the simulation, a master configuration utility (not part of this class),
// reads in configuration data for each software component, and sets the value
// for each configuration entry accordingly. The master configuration utility
// is intended read a proto as defined in
// //proto/component_data.proto. GCL (go/gcl)
// will be one method by which this proto can be generated.
namespace mpact {
namespace sim {
namespace generic {
struct PhysicalValue {
double value;
proto::SIPrefix si_prefix;
proto::SIUnit si_unit;
// Constructors.
explicit PhysicalValue(double value)
: PhysicalValue(value, proto::SIPrefix::PREFIX_NONE,
proto::SIUnit::UNIT_NONE) {}
PhysicalValue(double value, proto::SIUnit unit)
: PhysicalValue(value, proto::SIPrefix::PREFIX_NONE, unit) {}
PhysicalValue(double value_, proto::SIPrefix prefix, proto::SIUnit unit)
: value(value_), si_prefix(prefix), si_unit(unit) {}
};
// Variant value type of the config object. Only some types are supported.
// Additional types may be added in the future. If new types are added,
// the proto definition in component_data.proto will need to be updated
// accordingly, as well as adding additional specializations for the ExportValue
// and ImportValue methods at the bottom of this file.
using ConfigValue =
std::variant<bool, int64_t, uint64_t, double, std::string, PhysicalValue>;
// This is the base class for a configuration entry. The value is type agnostic,
// as it uses the variant class. This class is primarily intended as a handle
// to access the configuration entry from a registry where configuration entries
// of different types may be stored.
class ConfigBase {
public:
explicit ConfigBase(absl::string_view name) : name_(name) {}
ConfigBase() = delete;
ConfigBase(const ConfigBase &) = delete;
ConfigBase &operator=(const ConfigBase &) = delete;
virtual ~ConfigBase() = default;
// Return true if the config value has been set since construction.
virtual bool HasConfigValue() const = 0;
// Variant value accessors provide type agnostic access to config value.
virtual ConfigValue GetConfigValue() const = 0;
virtual absl::Status SetConfigValue(const ConfigValue &) = 0;
// Exports the Config (name and value) to the proto message.
virtual absl::Status Export(proto::ComponentValueEntry *entry) const = 0;
virtual absl::Status Import(const proto::ComponentValueEntry *entry) = 0;
const std::string &name() const { return name_; }
private:
std::string name_;
};
// The type specific class for the configuration entry. This class is templated
// on the type of the value. However, the template argument is restricted to
// those types that are in the ConfigValue variant. The static assert in the
// class enforces this and produces a reasonable error message in case an
// instantiation with a non supported type is attempted.
template <typename T>
class Config : public ConfigBase {
static_assert(IsMemberOfVariant<ConfigValue, T>::value,
"Template argument type is not in ConfigValue variant");
public:
using ValueWrittenCallback = std::function<void()>;
Config() = delete;
explicit Config(absl::string_view name) : ConfigBase(name) {}
Config(absl::string_view name, T value) : ConfigBase(name), value_(value) {}
~Config() override = default;
// Return true if the value has been updated since construction.
bool HasConfigValue() const override { return has_value_; }
// Get and Set the value of the configuration entry using the variant value.
// The SetConfigValue method returns an error status if the member of the
// variant in the config_value differs in type from that of the template
// parameter.
ConfigValue GetConfigValue() const override {
return ConfigValue(GetValue());
}
absl::Status SetConfigValue(const ConfigValue &config_value) override {
if (!std::holds_alternative<T>(config_value)) {
return absl::InvalidArgumentError("Invalid type in ConfigValue argument");
}
SetValue(std::get<T>(config_value));
has_value_ = true;
return absl::OkStatus();
}
// Get and Set the value using the value type. These are passed/returned by
// value. This is true even in the case where a std::string might be the value
// type. This is a) not the expected common case, and b) accessing the values
// of the configuration entries should not be on the critical path in any
// simulator anyway.
T GetValue() const { return value_; }
void SetValue(T value) {
has_value_ = true;
value_ = value;
for (auto const &callback : value_written_callback_vector_) {
callback();
}
}
// Add a callback on value written. Some configuration entries may be
// modifiable during simulation, for example, an adjustable trade-off between
// accuracy and speed, requiring a notification when the value changes. This
// supports this usecase. Note, the callback is made whenever the value is
// written to, not just if it changes.
template <typename F>
void AddValueWrittenCallback(F callback) {
value_written_callback_vector_.push_back(ValueWrittenCallback(callback));
}
// Exports the configuration entry name and value to the proto message.
absl::Status Export(proto::ComponentValueEntry *entry) const override {
if (entry == nullptr) return absl::InvalidArgumentError("Entry is null");
entry->set_name(name());
ExportValue(entry);
return absl::OkStatus();
}
// Imports the configuration entry value in the proto. Returns an error if the
// name doesn't match or the entry is nullptr.
absl::Status Import(const proto::ComponentValueEntry *entry) override {
if (entry == nullptr) return absl::InvalidArgumentError("Entry is null");
if (!entry->has_name() || (entry->name() != name()))
return absl::InternalError(
absl::StrCat("name mismatch: '", name(), "' != '",
(entry->has_name() ? entry->name() : ""), "'"));
auto status = ImportValue(entry);
if (!status.ok()) return status;
return absl::OkStatus();
}
private:
// Note, the following two methods are declared in this class, but defined
// in specializations outside the class, as each specialization requires
// writing to a different field in the proto message.
// Exports the value to the proto message.
void ExportValue(proto::ComponentValueEntry *entry) const;
// Imports the value from the proto message.
absl::Status ImportValue(const proto::ComponentValueEntry *entry);
bool has_value_ = false;
T value_;
std::vector<ValueWrittenCallback> value_written_callback_vector_;
};
// The following specializations for the ExportValue and ImportValue methods are
// declared as inline to avoid a warning for potentially generating an ODR
// violation.
// ExportValue() specializations for the types in the ConfigValue variant.
// NOTE: add a specialization for every new type added to the variant.
template <>
inline void Config<bool>::ExportValue(proto::ComponentValueEntry *entry) const {
entry->set_bool_value(GetValue());
}
template <>
inline void Config<int64_t>::ExportValue(
proto::ComponentValueEntry *entry) const {
entry->set_sint64_value(GetValue());
}
template <>
inline void Config<uint64_t>::ExportValue(
proto::ComponentValueEntry *entry) const {
entry->set_uint64_value(GetValue());
}
template <>
inline void Config<double>::ExportValue(
proto::ComponentValueEntry *entry) const {
entry->set_double_value(GetValue());
}
template <>
inline void Config<std::string>::ExportValue(
proto::ComponentValueEntry *entry) const {
entry->set_string_value(GetValue());
}
template <>
inline void Config<PhysicalValue>::ExportValue(
proto::ComponentValueEntry *entry) const {
auto *pvalue = entry->mutable_physical_value();
const PhysicalValue &value = GetValue();
pvalue->set_value(value.value);
pvalue->set_si_prefix(value.si_prefix);
pvalue->set_si_unit(value.si_unit);
}
// ImportValue() specializations for the types in the ConfigValue variant.
// NOTE: add a specialization for every new type added to the variant.
template <>
inline absl::Status Config<bool>::ImportValue(
const proto::ComponentValueEntry *entry) {
if (!entry->has_bool_value()) return absl::InternalError("No valid value");
SetValue(entry->bool_value());
return absl::OkStatus();
}
template <>
inline absl::Status Config<int64_t>::ImportValue(
const proto::ComponentValueEntry *entry) {
if (!entry->has_sint64_value()) return absl::InternalError("No valid value");
SetValue(entry->sint64_value());
return absl::OkStatus();
}
template <>
inline absl::Status Config<uint64_t>::ImportValue(
const proto::ComponentValueEntry *entry) {
if (!entry->has_uint64_value()) return absl::InternalError("No valid value");
SetValue(entry->uint64_value());
return absl::OkStatus();
}
template <>
inline absl::Status Config<double>::ImportValue(
const proto::ComponentValueEntry *entry) {
if (!entry->has_double_value()) return absl::InternalError("No valid value");
SetValue(entry->double_value());
return absl::OkStatus();
}
template <>
inline absl::Status Config<std::string>::ImportValue(
const proto::ComponentValueEntry *entry) {
if (!entry->has_string_value()) return absl::InternalError("No valid value");
SetValue(entry->string_value());
return absl::OkStatus();
}
template <>
inline absl::Status Config<PhysicalValue>::ImportValue(
const proto::ComponentValueEntry *entry) {
if (!entry->has_physical_value())
return absl::InternalError("No valid value");
SetValue(PhysicalValue(entry->physical_value().value(),
entry->physical_value().si_prefix(),
entry->physical_value().si_unit()));
return absl::OkStatus();
}
} // namespace generic
} // namespace sim
} // namespace mpact
#endif // MPACT_SIM_GENERIC_CONFIG_H_