diff --git a/.clang-format b/.clang-format index 5737aca1ec7df306124ffc6d805befe3ff6ade12..5c443be69c45337924324a1f56fad9721dac62f6 100644 --- a/.clang-format +++ b/.clang-format @@ -9,7 +9,7 @@ SpaceAfterControlStatementKeyword: true PointerBindsToType: true IncludeBlocks: Preserve UseTab: Never -ColumnLimit: 120 +ColumnLimit: 100 NamespaceIndentation: Inner AlignConsecutiveAssignments: true ... diff --git a/core/algorithms/logger.h b/core/algorithms/logger.h index db88f3074db1feb008d2ce888c04ea5a5c3ce4f2..2ad9e7759625f65c1a5427f09ada6cd74e7292fd 100644 --- a/core/algorithms/logger.h +++ b/core/algorithms/logger.h @@ -1,180 +1,165 @@ #pragma once +#include <array> +#include <functional> +#include <ios> #include <iostream> +#include <mutex> #include <ostream> -#include <ios> #include <sstream> #include <string> -#include <functional> -#include <mutex> -#include <array> #include <algorithms/service.h> -//#include <fmt/core.h> // Simple thread-safe logger with optional overrides by the calling framework namespace algorithms { -enum class LogLevel : unsigned { - kJunk = 0, - kDebug = 1, - kInfo = 2, - kWarning = 3, - kError = 4 -}; +enum class LogLevel : unsigned { kJunk = 0, kDebug = 1, kInfo = 2, kWarning = 3, kError = 4 }; constexpr std::string_view logLevelName(LogLevel level) { // Compiler will warn if not all of the enum is covered switch (level) { - case LogLevel::kJunk: return "JUNK"; - case LogLevel::kDebug: return "DEBUG"; - case LogLevel::kInfo: return "INFO"; - case LogLevel::kWarning: return "WARNING"; - case LogLevel::kError: return "ERROR"; + case LogLevel::kJunk: + return "JUNK"; + case LogLevel::kDebug: + return "DEBUG"; + case LogLevel::kInfo: + return "INFO"; + case LogLevel::kWarning: + return "WARNING"; + case LogLevel::kError: + return "ERROR"; } } -// TODO: integrate proper properties to configure the default level // Note: the log action is responsible for dealing with concurrent calls // the default LogAction is a thread-safe example class LogSvc : public ServiceMixin<LogSvc> { - public: - using LogAction = std::function<void(LogLevel, std::string_view, std::string_view)>; - void defaultLevel(const LogLevel l) {m_level = l;} - LogLevel defaultLevel() const {return m_level;} - void action(LogAction a) { - m_action = a; - } - void report(const LogLevel l, std::string_view caller, std::string_view msg) const { - m_action(l, caller, msg); - } +public: + using LogAction = std::function<void(LogLevel, std::string_view, std::string_view)>; + void defaultLevel(const LogLevel l) { m_level.set(l); } + LogLevel defaultLevel() const { return m_level; } + void action(LogAction a) { m_action = a; } + void report(const LogLevel l, std::string_view caller, std::string_view msg) const { + m_action(l, caller, msg); + } - private: - LogLevel m_level = LogLevel::kInfo; - LogAction m_action = [](const LogLevel l, std::string_view caller, std::string_view msg) { - static std::mutex m; - std::lock_guard<std::mutex> lock(m); - //fmt::print("%s [%s] %s\n", logLevelName(l), caller, msg); - std::cout << logLevelName(l) << " [" << caller << "] " << msg << std::endl; - }; +private: + Property<LogLevel> m_level{this, "defaultLevel", LogLevel::kInfo}; + LogAction m_action = [](const LogLevel l, std::string_view caller, std::string_view msg) { + static std::mutex m; + std::lock_guard<std::mutex> lock(m); + // fmt::print("%s [%s] %s\n", logLevelName(l), caller, msg); + std::cout << logLevelName(l) << " [" << caller << "] " << msg << std::endl; + }; ALGORITHMS_DEFINE_SERVICE(LogSvc) }; namespace detail { // Output buffer that calls our global logger's report() function class LoggerBuffer : public std::stringbuf { - public: - LoggerBuffer(const LogLevel l, std::string_view caller) : m_mylevel{l}, m_caller{caller}, m_logger{algorithms::LogSvc::instance()} {} - virtual int sync() { - // report should deal with concurrency (the minimal version does) - m_logger.report(m_mylevel, m_caller, this->str()); - this->str(""); - return 0; - } - private: - // The output buffer knows the log level of its associated logger - // (eg. is this the debug logger?) - LogLevel m_mylevel; - const std::string m_caller; - const LogSvc& m_logger; + public: + LoggerBuffer(const LogLevel l, std::string_view caller) + : m_mylevel{l}, m_caller{caller}, m_logger{algorithms::LogSvc::instance()} {} + virtual int sync() { + // report should deal with concurrency (the minimal version does) + m_logger.report(m_mylevel, m_caller, this->str()); + this->str(""); + return 0; + } + + private: + // The output buffer knows the log level of its associated logger + // (eg. is this the debug logger?) + LogLevel m_mylevel; + const std::string m_caller; + const LogSvc& m_logger; }; // thread-safe output stream for the logger class LoggerStream { - public: - LoggerStream(std::string_view caller, const LogLevel level, const LogLevel threshold = LogSvc::instance().defaultLevel()) - : m_buffer{level, caller} - , m_os{&m_buffer} - , m_level{level} - , m_threshold{threshold} {} - LoggerStream() = delete; - LoggerStream(const LoggerStream&) = delete; - - template <class Arg> - LoggerStream& operator<<(Arg&& streamable) { - if (m_level >= m_threshold) { - std::lock_guard<std::mutex> lock{m_mutex}; - m_os << std::forward<Arg>(streamable); - return *this; - } + public: + LoggerStream(std::string_view caller, const LogLevel level, + const LogLevel threshold = LogSvc::instance().defaultLevel()) + : m_buffer{level, caller}, m_os{&m_buffer}, m_level{level}, m_threshold{threshold} {} + LoggerStream() = delete; + LoggerStream(const LoggerStream&) = delete; + + template <class Arg> LoggerStream& operator<<(Arg&& streamable) { + if (m_level >= m_threshold) { + std::lock_guard<std::mutex> lock{m_mutex}; + m_os << std::forward<Arg>(streamable); return *this; } - // To support input manipulators such as std::endl - // Note: would be better with Concepts - using IOManipType1 = std::ostream&(std::ostream&); // this capturs std::endl; - using IOManipType2 = std::ios_base&(std::ios_base&); - LoggerStream& operator<<(IOManipType1* f) { - if (m_level >= m_threshold) { - std::lock_guard<std::mutex> lock{m_mutex}; - f(m_os); - return *this; - } + return *this; + } + // To support input manipulators such as std::endl + // Note: would be better with Concepts + using IOManipType1 = std::ostream&(std::ostream&); // this capturs std::endl; + using IOManipType2 = std::ios_base&(std::ios_base&); + LoggerStream& operator<<(IOManipType1* f) { + if (m_level >= m_threshold) { + std::lock_guard<std::mutex> lock{m_mutex}; + f(m_os); return *this; } - LoggerStream& operator<<(IOManipType2* f) { - if (m_level >= m_threshold) { - std::lock_guard<std::mutex> lock{m_mutex}; - f(m_os); - return *this; - } + return *this; + } + LoggerStream& operator<<(IOManipType2* f) { + if (m_level >= m_threshold) { + std::lock_guard<std::mutex> lock{m_mutex}; + f(m_os); return *this; } - LogLevel threshold() const {return m_threshold;} - void threshold(const LogLevel th) {m_threshold = th;} - - private: - std::mutex m_mutex; - LoggerBuffer m_buffer; - std::ostream m_os; - const LogLevel m_level; - LogLevel m_threshold; + return *this; + } + LogLevel threshold() const { return m_threshold; } + void threshold(const LogLevel th) { m_threshold = th; } + + private: + std::mutex m_mutex; + LoggerBuffer m_buffer; + std::ostream m_os; + const LogLevel m_level; + LogLevel m_threshold; }; -} +} // namespace detail // Mixin meant to add utility logger functions to algorithms/services/etc -// TODO: add property to configure log level instead of using member function class LoggerMixin { - public: - LoggerMixin(std::string_view caller, const LogLevel threshold = LogSvc::instance().defaultLevel()) +public: + LoggerMixin(std::string_view caller, const LogLevel threshold = LogSvc::instance().defaultLevel()) : m_caller{caller} { - level(threshold); - } - public: - void level(const LogLevel threshold) { - m_level = threshold; - m_error.threshold(m_level); - m_warning.threshold(m_level); - m_info.threshold(m_level); - m_debug.threshold(m_level); - m_junk.threshold(m_level); - } - LogLevel level() const {return m_level;} + level(threshold); + } - protected: - detail::LoggerStream& error() { - return m_error; - } - detail::LoggerStream& warning() { - return m_warning; - } - detail::LoggerStream& info() { - return m_info; - } - detail::LoggerStream& debug() { - return m_debug; - } - detail::LoggerStream& junk() { - return m_junk; - } +public: + // Not done through Properties, as that is the responsible of the base Algo or Service + void level(const LogLevel threshold) { + m_level = threshold; + m_error.threshold(m_level); + m_warning.threshold(m_level); + m_info.threshold(m_level); + m_debug.threshold(m_level); + m_junk.threshold(m_level); + } + LogLevel level() const { return m_level; } - private: - const std::string m_caller; - LogLevel m_level; - detail::LoggerStream m_error{m_caller, LogLevel::kError}; - detail::LoggerStream m_warning{m_caller, LogLevel::kWarning}; - detail::LoggerStream m_info{m_caller, LogLevel::kInfo}; - detail::LoggerStream m_debug{m_caller, LogLevel::kDebug}; - detail::LoggerStream m_junk{m_caller, LogLevel::kJunk}; +protected: + detail::LoggerStream& error() const { return m_error; } + detail::LoggerStream& warning() const { return m_warning; } + detail::LoggerStream& info() const { return m_info; } + detail::LoggerStream& debug() const { return m_debug; } + detail::LoggerStream& junk() const { return m_junk; } + +private: + const std::string m_caller; + LogLevel m_level; + mutable detail::LoggerStream m_error{m_caller, LogLevel::kError}; + mutable detail::LoggerStream m_warning{m_caller, LogLevel::kWarning}; + mutable detail::LoggerStream m_info{m_caller, LogLevel::kInfo}; + mutable detail::LoggerStream m_debug{m_caller, LogLevel::kDebug}; + mutable detail::LoggerStream m_junk{m_caller, LogLevel::kJunk}; }; -} +} // namespace algorithms #define endmsg std::flush diff --git a/core/algorithms/property.h b/core/algorithms/property.h index 7439fb093f7dc39b67c8155c465050e85a4ed77b..9da6b99d9e7f993cf670bb47f5384b72fa5956ec 100644 --- a/core/algorithms/property.h +++ b/core/algorithms/property.h @@ -3,59 +3,141 @@ #include <any> #include <map> #include <string> +#include <vector> namespace algorithms { -// A property type that auto-registers itself with the property handler -template <class T> -class Property { +// Configuration/property handling +class Configurable { +public: + class PropertyBase; + using PropertyMap = std::map<std::string_view, PropertyBase&>; + + template <typename T, typename U> void setProperty(std::string_view name, U&& value) { + m_props.at(name).set(T(std::forward<U>(value))); + } + template <typename T> T getProperty(std::string_view name) const { + return std::any_cast<T>(m_props.at(name).get()); + } + const PropertyMap& getProperties() const { return m_props; } + bool hasProperty(std::string_view name) const { + return m_props.count(name) && m_props.at(name).hasValue(); + } + +private: + void registerProperty(PropertyBase& prop) { + // TODO ensure we have unique property names + m_props.emplace(prop.name(), prop); + } + + PropertyMap m_props; + +public: + class PropertyBase { + public: + PropertyBase(std::string_view name) : m_name{name} {} + virtual void set(std::any v) = 0; + virtual std::any get() const = 0; + bool hasValue() const { return m_has_value; } + std::string_view name() const { return m_name; } + + protected: + std::string_view m_name; + bool m_has_value = false; + }; + + // A property type that auto-registers itself with the property handler + // Essentially a simplified and const-like version of Gaudi::Property + template <class T> class Property : public PropertyBase { public: using ValueType = T; - // Get const reference to the value - const ValueType& value() const { return m_value; } + Property(Configurable* owner, std::string_view name) : PropertyBase{name} { + // TODO what if we have no owner? warn? error? + if (owner) { + owner->registerProperty(*this); + } + } + Property(Configurable* owner, std::string_view name, const ValueType& v) + : Property(owner, name) { + set(v); + } - std::string_view name() const { return m_name; } + Property() = delete; + Property(const Property&) = delete; + void operator=(const Property&) = delete; // Only settable by explicitly calling the value operator // as we want the Property to mostly act as if it is constant - template <class U> void value(U&& v) { m_value = std::forward<U>(v); } + virtual void set(std::any v) { + m_value = std::any_cast<T>(v); + m_has_value = true; + } + // virtual getter for use from PropertyBase - use ::value() instead for a quick + // version that does not go through std::any + // Get const reference to the value + virtual std::any get() const { return m_value; } + const ValueType& value() const { return m_value; } + + // automatically cast to T + operator T() const {return m_value;} // act as if this is a const T - template <typename U> bool operator==(const U& rhs) const { return m_value == m_value; } - template <typename U> bool operator!=(const U& rhs) const { return m_value != m_value; } + template <typename U> bool operator==(const U& rhs) const { return m_value == rhs; } + template <typename U> bool operator!=(const U& rhs) const { return m_value != rhs; } template <typename U> bool operator>(const U& rhs) const { return m_value > rhs; } template <typename U> bool operator>=(const U& rhs) const { return m_value >= rhs; } template <typename U> bool operator<(const U& rhs) const { return m_value < rhs; } template <typename U> bool operator<=(const U& rhs) const { return m_value <= rhs; } - template <typename U> auto operator+(const U& rhs) const { return m_value + rhs; } - template <typename U> auto operator-(const U& rhs) const { return m_value - rhs; } - template <typename U> auto operator*(const U& rhs) const { return m_value * rhs; } - template <typename U> auto operator/(const U& rhs) const { return m_value / rhs; } + template <typename U> decltype(auto) operator+(const U& rhs) const { return m_value + rhs; } + template <typename U> decltype(auto) operator-(const U& rhs) const { return m_value - rhs; } + template <typename U> decltype(auto) operator*(const U& rhs) const { return m_value * rhs; } + template <typename U> decltype(auto) operator/(const U& rhs) const { return m_value / rhs; } - private: - std::string_view m_name; - T m_value; -}; + // stl collection helpers if needed + // forced to be templated so we only declare them when used + template <class U = const ValueType> decltype(auto) size() const { return value().size(); } + template <class U = const ValueType> decltype(auto) length() const { return value().length(); } + template <class U = const ValueType> decltype(auto) empty() const { return value().empty(); } + template <class U = ValueType> decltype(auto) clear() { value().clear(); } + template <class U = const ValueType> decltype(auto) begin() const { return value().begin(); } + template <class U = const ValueType> decltype(auto) end() const { return value().end(); } + template <class U = ValueType> decltype(auto) begin() { return value().begin(); } + template <class U = ValueType> decltype(auto) end() { return value().end(); } + template <class Arg> decltype(auto) operator[](const Arg& arg) const { return value()[arg]; } + template <class Arg> decltype(auto) operator[](const Arg& arg) { return value()[arg]; } + template <class U = const ValueType> + decltype(auto) find(const typename U::key_type& key) const { + return value().find(key); + } + template <class U = ValueType> decltype(auto) find(const typename U::key_type& key) { + return value().find(key); + } + template <class Arg> decltype(auto) erase(const Arg& arg) { return value().erase(arg); } -// Configuration/property handling -class Configurable { - public: + // In case our property has operator (), delegate the operator() + template <class... Args> + decltype(std::declval<ValueType>()(std::declval<Args&&>()...)) + operator()(Args&&... args) const { + return m_value()(std::forward<Args>(args)...); + } private: - std::map<std::string_view, std::any> m_props; + T m_value; + }; }; -// Property mixin, provides all the configuration functionality for +// Property mixin, provides all the configuration functionality for // our algorithms and services // Currently an alias to Configurable using PropertyMixin = Configurable; -} +} // namespace algorithms // operator== overload not needed in C++20 as it will call the member version #if __cpp_impl_three_way_comparison < 201711 -template <class T, class U> bool operator==(const U& rhs, algorithms::Property<T>& p) { +template <class T, class U> +bool operator==(const U& rhs, algorithms::Configurable::Property<T>& p) { return p == rhs; } #endif diff --git a/core/algorithms/test.cpp b/core/algorithms/test.cpp index 245bf9ccc99139bbed815187c03f140398dd2f6d..0ef0216417421f8d5b9df3247b0b7722879aa9f9 100644 --- a/core/algorithms/test.cpp +++ b/core/algorithms/test.cpp @@ -2,10 +2,13 @@ using namespace algorithms; -class testAlgo : public LoggerMixin { +// TODO implement PropertyBase? or use boost::any? need something better for setProperty +// so we don't need to repeat the type unnecessarily. Hard without type erasure though... + +class testLogger : public LoggerMixin { public: - testAlgo() - : LoggerMixin("testAlgo") + testLogger() + : LoggerMixin("testLogger") { std::cout << "should display an info message" << std::endl; info() << "1" << endmsg; @@ -25,8 +28,48 @@ class testAlgo : public LoggerMixin { } }; +class propTest : public PropertyMixin, LoggerMixin { + public: + propTest() : LoggerMixin("propTest") {} + void print() const { + info() << "integer property: " << getProperty<int>("integer") << endmsg; + info() << "foo property: " << getProperty<std::string>("foo") << endmsg; + if (hasProperty("bar")) { + info() << "bar property: " << getProperty<std::string>("bar") << endmsg; + } + if (hasProperty("double")) { + info() << "double (non-def.) property: " << getProperty<double>("double") << endmsg; + } + } + private: + Property<int> m_int {this, "integer", 3}; + Property<std::string> m_foo{this, "foo", "foo_property_value"}; + // and one without a default + Property<std::string> m_bar{this, "bar"}; + Property<double> m_double{this, "double"}; + +}; + int main() { - testAlgo t; + testLogger tl; + + std::cout << "test of property, will print set properties. Should only print 2 to start (no bar, as it does not have a default value\n"; + propTest tp; + tp.print(); + tp.setProperty<int>("integer", 10); + tp.print(); + try { + tp.setProperty<int>("foo", 1.); + std::cout << "Should not have been reached, as foo does not exist" << std::endl; + return 1; + } catch (...) { + std::cout << "setting a property that didn't exist failed popperly" << std::endl; + } + std::cout << "Let's now also set a value for the third and fourth property so it gets printed as well" << std::endl; + tp.setProperty<std::string>("bar", "bar_value"); + tp.setProperty<double>("double", 3.1415f); + tp.print(); + return 0; }