Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2024-06-26 07:05:12

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Wouter Deconinck, Sylvester Joosten
0003 //
0004 // Basic Service infrastructure, implemented as lazy-evaluated singletons (thread-safe).
0005 //
0006 // Provides the special ServiceSvc (that provides access to all instantiated services as
0007 // Configurable*), and the ServiceMixin (and related ALGORITHMS_DEFINE_SERVICE macro).
0008 //
0009 #pragma once
0010 
0011 #include <fmt/format.h>
0012 #include <fmt/ranges.h>
0013 #include <functional>
0014 #include <map>
0015 #include <string>
0016 
0017 #include <algorithms/error.h>
0018 #include <algorithms/name.h>
0019 #include <algorithms/property.h>
0020 
0021 // Add boilerplate to service class definitions
0022 //  - singleton --> no public constructor, no copy constructor, no assigmnent operator
0023 //  - constructor should be protected so we can actually inherit from this class if needed
0024 //    (mostly needed for the service base class)
0025 #define ALGORITHMS_DEFINE_SERVICE(className)                                                       \
0026 protected:                                                                                         \
0027   className() : Service<className>(#className) {}                                                  \
0028                                                                                                    \
0029 public:                                                                                            \
0030   friend class Service<className>;                                                                 \
0031   className(const className&) = delete;                                                            \
0032   void operator=(const className&)   = delete;                                                     \
0033   constexpr static const char* kName = #className;
0034 
0035 namespace algorithms {
0036 
0037 class ServiceError : public Error {
0038 public:
0039   ServiceError(std::string_view msg) : Error{msg, "algorithms::ServiceError"} {}
0040 };
0041 
0042 class ServiceBase : public PropertyMixin {
0043 public:
0044   // Is this service ready? (active and initialized)
0045   bool ready() const { return m_ready; }
0046   void ready(bool state) { m_ready = state; }
0047 
0048 private:
0049   bool m_ready{false};
0050 };
0051 
0052 // Service service --> keeps track of all services :)
0053 //                 --> exposes the underlying ServiceBase* object of the service so
0054 //                     we can configure the services by name in the framework
0055 //                     boundary plugin
0056 //                 --> Special service (does not use the Service base class to avoid
0057 //                     circularity)
0058 class ServiceSvc : public NameMixin {
0059 public:
0060   using ServiceMapType = std::map<std::string_view, ServiceBase*>;
0061   static ServiceSvc& instance() {
0062     // This is guaranteed to be thread-safe from C++11 onwards.
0063     static ServiceSvc svc;
0064     return svc;
0065   }
0066   // add a service to the ServiceSvc. Two options:
0067   // - prior to init --> just add the service to the service stack and set the initializer
0068   // - after init    --> add service to call stack, set the initializer,
0069   //                     manually call init for the service, revalidate
0070   template <class Svc>
0071   void add(
0072       ServiceBase* svc, const std::function<void(Svc&)>& init = [](Svc& s) { s.init(); }) {
0073     m_keys.push_back(Svc::kName);
0074     m_services[Svc::kName] = svc;
0075     // only add initializer if not already present (e.g. if for some reason the framework did
0076     // the registration early)
0077     if (m_initializers.count(Svc::kName) == 0) {
0078       setInit(init);
0079     }
0080     // call init if we are already in an initialized state (this is a straggler)
0081     if (m_init) {
0082       initSingle<Svc>();
0083       validate();
0084     }
0085   }
0086 
0087   template <class Svc> bool has() const { return m_services.count(Svc::kName); }
0088   template <class Svc> void setInit(const std::function<void(Svc&)>& init) {
0089     m_initializers[Svc::kName] = [init]() { init(Svc::instance()); };
0090   }
0091 
0092   // Loop over all services in order and initialize one-by-one
0093   // Finalize by validating that all is well
0094   void init() {
0095     // ensure we only call init once
0096     if (m_init) {
0097       throw ServiceError("Cannot initialize services twice");
0098     }
0099     // Call init for all the services and mark as ready
0100     for (const auto& name : m_keys) {
0101       try {
0102         m_initializers.at(name)();
0103       } catch (const std::exception& e) {
0104         // we encountered an issue, stop here so validation fails
0105         break;
0106       }
0107       auto svc = m_services.at(name);
0108       // Ensure our init made sense -- cannot have missing properties at this stage
0109       if (svc->missingProperties().size() > 0) {
0110         break;
0111       }
0112       svc->ready(true);
0113     }
0114 
0115     // Validate all services in case we encountered issues so we get useful error
0116     // reporting
0117     validate();
0118 
0119     // Label initialization as complete
0120     m_init = true;
0121   }
0122   // Single service init (meant to be called for stragglers after general init)
0123   template <class Svc> void initSingle() {
0124     std::string_view name = Svc::kName;
0125     if (!m_init) {
0126       throw ServiceError(
0127           fmt::format("initSingle<{}>() should not be called before/instead of init()", name));
0128     }
0129     auto svc = m_services.at(name);
0130     // ensure we only call init once
0131     if (svc->ready()) {
0132       throw ServiceError(fmt::format("Cannot initialize service {} - already initialized", name));
0133     }
0134     // call init
0135     m_initializers.at(name)();
0136     // mark as ready
0137     svc->ready(true);
0138   }
0139 
0140   template <class Svc = ServiceBase> Svc* service(std::string_view name) const {
0141     return static_cast<Svc*>(m_services.at(name));
0142   }
0143   // Check if all service properties are set
0144   // TODO FIXME move to implementation file
0145   void validate() const {
0146     std::map<std::string_view, std::vector<std::string_view>> missing_props;
0147     std::vector<std::string_view> not_initialized;
0148     for (std::string_view name : m_keys) {
0149       const auto svc = m_services.at(name);
0150       auto missing   = svc->missingProperties();
0151       if (!missing.empty()) {
0152         missing_props[name] = missing;
0153       }
0154       if (!svc->ready()) {
0155         not_initialized.push_back(name);
0156       }
0157       std::string err = "";
0158       if (missing_props.size() > 0) {
0159         err += fmt::format("Encountered missing service properties: {}\n", missing_props);
0160       }
0161       if (not_initialized.size() > 0) {
0162         err += fmt::format("Encountered uninitialized services: {}\n", not_initialized);
0163       }
0164       if (err.size() > 0) {
0165         throw ServiceError(fmt::format("Error initializing all services:\n{}", err));
0166       }
0167     }
0168   }
0169   const auto& services() const { return m_services; }
0170   bool ready() const {
0171     for (const auto& key : m_keys) {
0172       if (!m_services.at(key)->ready()) {
0173         return false;
0174       }
0175     }
0176     return true;
0177   }
0178 
0179 private:
0180   ServiceSvc() : NameMixin{"ServiceSvc", "Special service that keeps track of all services"} {}
0181 
0182 public:
0183   ServiceSvc(const ServiceSvc&) = delete;
0184   void operator=(const ServiceSvc&) = delete;
0185 
0186 private:
0187   std::vector<std::string_view> m_keys;                // Ordered list of service keys
0188   std::map<std::string_view, ServiceBase*> m_services; // Map of services for easier lookup
0189   std::map<std::string_view, std::function<void()>> m_initializers; // Init calls
0190   bool m_init = false; // did we initialize the services already?
0191 };
0192 
0193 // Thread-safe lazy-evaluated minimal service system
0194 // CRTP base class to add the instance method
0195 // This could have been part of DEFINE_SERVICE macro, but I think it is better
0196 // to keep the macro magic to a minimum to maximize transparency
0197 template <class SvcType> class Service : public ServiceBase, public NameMixin {
0198 public:
0199   static SvcType& instance() {
0200     // This is guaranteed to be thread-safe from C++11 onwards.
0201     static SvcType svc;
0202     return svc;
0203   }
0204   // constructor for the service base class registers the service, except
0205   // for the ServiceSvc which is its own thing (avoid circularity)
0206   // (services don't currently have an attached description)
0207   Service(std::string_view name) : NameMixin{name, name} {
0208     ServiceSvc::instance().add<SvcType>(this);
0209   }
0210 };
0211 
0212 } // namespace algorithms
0213