/******************************************************************************** * Copyright (c) 2025-2026 ZF Friedrichshafen AG * * This program and the accompanying materials are made available under the * terms of the Apache License Version 2.0 which is available at * https://www.apache.org/licenses/LICENSE-2.0 * * SPDX-License-Identifier: Apache-2.0 * * Contributors: * Erik Verhoeven - initial API and implementation ********************************************************************************/ #ifndef COMPONENT_IMPL_H #define COMPONENT_IMPL_H #include #include #include #include #include #include #include "../interfaces/core_types.h" #include "../interfaces/core.h" #include "../interfaces/repository.h" #include "sdv_core.h" #include "interface_ptr.h" #include "local_service_access.h" #include "param_impl.h" #include "toml.h" // NOLINTBEGIN(cppcoreguidelines-macro-usage) /** * @brief Stringize helper. */ #define SDV_STRINGIZE_HELPER2(txt) #txt /** * @brief Stringize helper. */ #define SDV_STRINGIZE_HELPER(txt) SDV_STRINGIZE_HELPER2(txt) // NOLINTEND(cppcoreguidelines-macro-usage) #ifdef _WIN32 // Resolve conflict #pragma push_macro("interface") #undef interface #pragma push_macro("GetObject") #undef GetObject #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include // Resolve conflict #pragma pop_macro("GetObject") #pragma pop_macro("interface") #ifdef GetClassInfo #undef GetClassInfo #endif #elif defined __unix__ #else #error OS is not supported! #endif // defined _MSC_VER /** * @brief Software Defined Vehicle framework. */ namespace sdv { // Forward declarations class CSdvObject; //////////////////////////////////// // Object control classes // //////////////////////////////////// /** * @brief SDV object create interface. * @remarks This is an internal interface not exposed through IInterfaceAccess. */ struct ISdvObjectClassInfo { public: /** * @brief Get the class information struct. * @return Returns the class information struct. */ virtual SClassInfo GetClassInfo() const = 0; /** * @brief Get the object class name. * @return Returns the class name of the object. * @remarks Default implementation gets the information from the object class if not defined in the definition. */ virtual u8string GetClassName() const = 0; /** * @brief Get the class name aliases. * @return Returns a sequence containing zero or more class name aliases. */ virtual sequence GetClassAliases() const = 0; /** * @brief Get the default object name. * @return Returns the default name of the object if exists. */ virtual u8string GetDefaultObjectName() const = 0; /** * @brief Get the default config TOML string. * @param[in] rssTablePrefix Reference to the string containing a table prefix to insert as part of the table key. * @return Returns the TOML string containing the default configuration of the object if exists. */ virtual u8string GetDefaultConfig(const u8string& rssTablePrefix = {}) const = 0; /** * @brief Get the object type. * @return Returns the type of the object */ virtual EObjectType GetObjectType() const = 0; /** * @brief Get the object creation flags. * @return Returns the current flags of the object */ virtual uint32_t GetObjectFlags() const = 0; /** * @brief Get object dependencies. * @return Returns a vector containing the class names of the objects this component is dependent on. */ virtual sequence GetObjectDependencies() const = 0; /** * @brief Create the SDV object. * @return Returns an instance of the SDV object or nullptr when the object could not be created. */ virtual std::unique_ptr CreateObject() = 0; }; /** * @brief Top level SDV object to solve ambiguities in the use of IInterfaceAccess. THis class is used by * CSdvObjectClass during the instantiation of the SDV object. */ class CSdvObjectAccess : public IInterfaceAccess { public: /** * @brief Default constructor */ CSdvObjectAccess() = default; CSdvObjectAccess(CSdvObjectAccess&) = delete; CSdvObjectAccess& operator=(CSdvObjectAccess&) = delete; CSdvObjectAccess(CSdvObjectAccess&&) = delete; CSdvObjectAccess& operator=(CSdvObjectAccess&&) = delete; /** * @brief Default virtual destructor */ virtual ~CSdvObjectAccess() = default; /** * @brief Get access to the implemented IInterfaceAccess interface. * @return Returns a pointer to the implementation of IInterfaceAccess. */ IInterfaceAccess* GetObjectAccess() { return static_cast(this); } // Interface map BEGIN_SDV_INTERFACE_MAP() END_SDV_INTERFACE_MAP() }; /** * @brief Lifetime cookie to automatically manage the module lifetime. * @details The lifetime cookie uses an increment and decrement function to increment for every assigned instantiation of * the cookie and to decrement every previously instantiated cookie. This allows the classfactory to keep track of its lifetime * even with dynamically created objects. */ class CLifetimeCookie { public: /** * @brief Default constructor. */ CLifetimeCookie() = default; /** * @brief Assignment constructor, assigning the increment and decrement functions. * @details Assign the increment and decrement functions and increase the incrementation. * @param[in] fnLifetimeIncr Increment function object. * @param[in] fnLifetimeDecr Decrement function object. */ CLifetimeCookie(std::function fnLifetimeIncr, std::function fnLifetimeDecr) : m_fnLifetimeIncr(fnLifetimeIncr), m_fnLifetimeDecr(fnLifetimeDecr) { if (m_fnLifetimeIncr) m_fnLifetimeIncr(); } /** * @brief Copy constructor. * @details Construct a lifetime cookie object and copy the increment and decrement functions and call the increment * function if valid. * @param[in] rCookie Reference to the cookie to copy from. */ CLifetimeCookie(const CLifetimeCookie& rCookie) : m_fnLifetimeIncr(rCookie.m_fnLifetimeIncr), m_fnLifetimeDecr(rCookie.m_fnLifetimeDecr) { if (m_fnLifetimeIncr) m_fnLifetimeIncr(); } /** * @brief Move constructor. * @details Construct a lifetime cookie object and move the functions from the provided cookie. Then reset the functions * of the provided cookie. * @param[in] rCookie Reference to the cookie to copy from. */ CLifetimeCookie(CLifetimeCookie&& rCookie) noexcept : m_fnLifetimeIncr(rCookie.m_fnLifetimeIncr), m_fnLifetimeDecr(rCookie.m_fnLifetimeDecr) { rCookie.m_fnLifetimeIncr = nullptr; rCookie.m_fnLifetimeDecr = nullptr; } /** * @brief Destroy the lifetime cookie. * @details Decrement the lifetime by calling the decrement function if valid and destroy the cookie object. */ ~CLifetimeCookie() { if (m_fnLifetimeDecr) m_fnLifetimeDecr(); } /** * @brief Assignment operator. * @details Release the current lifetime by calling the decrement function and assign the increment and decrement functions * of the provided cookie and call the increment function if valid. * @param[in] rCookie Reference to the cookie to copy from. * @return Reference to this class. */ CLifetimeCookie& operator=(const CLifetimeCookie& rCookie) { if (m_fnLifetimeDecr) m_fnLifetimeDecr(); m_fnLifetimeIncr = rCookie.m_fnLifetimeIncr; m_fnLifetimeDecr = rCookie.m_fnLifetimeDecr; if (m_fnLifetimeIncr) m_fnLifetimeIncr(); return *this; } /** * @brief Move operator. * @details Release the current lifetime by calling the decrement function and move the functions from the provided cookie. * Then reset the functions of the provided cookie. * @param[in] rCookie Reference to the cookie to copy from. * @return Reference to this class. */ CLifetimeCookie& operator=(CLifetimeCookie&& rCookie) noexcept { if (m_fnLifetimeDecr) m_fnLifetimeDecr(); m_fnLifetimeIncr = rCookie.m_fnLifetimeIncr; m_fnLifetimeDecr = rCookie.m_fnLifetimeDecr; rCookie.m_fnLifetimeIncr = nullptr; rCookie.m_fnLifetimeDecr = nullptr; return *this; } private: std::function m_fnLifetimeIncr; ///< Lifetime increment function std::function m_fnLifetimeDecr; ///< Lifetime decrement function }; /** * @brief Global tracking of active objects. Functions in this class will only be called by CSdvObject and CSdvObjectClass */ class CObjectFactory : public IInterfaceAccess, public IObjectFactory { // Friend class SDV object and definition friend class CSdvObject; ///< SDV object base class. template friend class CSdvObjectClass; ///< SDV object creator class. public: /** * @brief Constructor */ CObjectFactory() = default; /** * @brief Interface map. */ BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_ENTRY(IObjectFactory) END_SDV_INTERFACE_MAP() /** * @brief Create a lifetime cookie that keeps track of the module lifetime as long as it is alive. * @return Returns a lifetime cookie. */ CLifetimeCookie CreateLifetimeCookie() { return CLifetimeCookie([this]() { m_uiActiveObjectCount++; }, [this]() { if (m_uiActiveObjectCount) m_uiActiveObjectCount--; }); } /** * @brief Get the amount of active SDV objects. * @return Returns the number of active SDV objects. */ uint32_t GetActiveObjects() const { return m_uiActiveObjectCount; } /** * @brief Get the module manifest. * @return Returns the pointer to a zero terminated string containing the module manifest or NULL when there is no string. */ const char* GetManifest() { if (m_ssManifest.empty()) BuildManifest(); return m_ssManifest.c_str(); } /** * @brief The object class names implemented in the object. Overload of sdv::IObjectFactory::GetClassNames. * @return Sequence with object class names string. */ virtual sequence GetClassNames() const override; /** * @brief Get the class information. Overload of sdv::IObjectFactory::GetClassInfo. * @param[in] ssClassName The name of the class object to get the class information for. * @return Returns the class information struct. */ virtual SClassInfo GetClassInfo(const u8string& ssClassName) const override; /** * @brief Create or get the object using the name from the object class info. Overload of * sdv::IObjectFactory::CreateObject. * @attention The objects lifetime is ended by a call to the DestroyObject function or the unloading of he module. * @param[in] ssClassName The name of the class object to instantiate. * @return Pointer to IInterfaceAccess interface of the object or NULL when the requested object doesn't exist. */ virtual IInterfaceAccess* CreateObject(const u8string& ssClassName) override; /** * @brief Destroy an instantiated object using the name of the object class info. Overload of * sdv::IObjectFactory::DestroyObject. * @param[in] object The object to destroy. */ virtual void DestroyObject(IInterfaceAccess* object) override; /** * @brief Destroys all active objects in reverse order of creation */ virtual void DestroyAllObjects() override; protected: /** * @brief Build the module manifest. */ void BuildManifest(); /** * @brief Expose the object prototype by placing it into the object prototype list. * @details Expose the object class to allow class access. This function is called by the constructor of the * class object. * @param[in] pClassInfo The object class internal interface. */ void ExposeObjectClass(ISdvObjectClassInfo* pClassInfo); /** * @brief Revoke the object prototype from the object prototype list. * @param[in] pClassInfo The object class internal interface. */ void RevokeObjectClass(const ISdvObjectClassInfo* pClassInfo); /** * @brief Quote the provided text following the string rules of TOML. * @param[in] rssText Reference to the text to quote. * @return The quoted text string. */ static std::string QuoteText(const std::string& rssText); private: std::atomic m_uiActiveObjectCount{0}; ///< The amount of active SDV objects. mutable std::mutex m_mtxActiveObjects; ///< Synchronize access to m_vecActiveObjects std::vector> m_vecActiveObjects; ///< List of objects actively kept alive mutable std::shared_mutex m_mtxObjectClasses; ///< Synchronize access to the object classes. std::vector m_vecObjectClasses; ///< List of object classes contained in this module. std::string m_ssManifest; ///< Manifest of this module. }; //////////////////////////////////// // Framework module classes // //////////////////////////////////// /** * @brief Class that manages the object instantiation and module lifetime. * @remarks A pointer to an instance of this class is exposed across dll boundaries and used to implement * CModuleInstance for SDV DLLs * The instance is created by the DEFINE_SDV_OBJECT macro and the macro also registers a helper capable of * instantiating the SDV object with the instance (see CObjectFactory for details) */ class CModule : public CSdvObjectAccess, public CObjectFactory { public: /** * @brief Default constructor */ CModule() = default; /** * @brief Interface map. */ BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_CHAIN_BASE(CObjectFactory) END_SDV_INTERFACE_MAP() /** * @brief Check whether the requested interface version corresponds to the one implemented here and return this * object. * @param[in] interfaceVersion The version of module interface that is requested for. * @return Returns the IInterfaceAccess interface of the module if the interface versions match, nullptr otherwise */ IInterfaceAccess* GetModuleControl(uint32_t interfaceVersion) { // No compatibility interfacing available yet. return interfaceVersion == SDVFrameworkInterfaceVersion ? GetObjectAccess() : nullptr; } }; /** * @brief Module access (global per module). * @return Reference to the global module. */ inline CModule& GetModule() { static CModule module; return module; } /** * @brief Create a lifetime cookie that keeps track of the module lifetime as long as it is alive. * @return Returns a lifetime cookie. */ inline CLifetimeCookie CreateLifetimeCookie() { return GetModule().CreateLifetimeCookie(); } //////////////////////////// // Component object class // //////////////////////////// /** * @brief SDV object creator class. Used to define the SDV class object and register its definition into the system * as welll a as providing the creation interface for creating an object instance. * @tparam TSdvObject Class type of the SDV object derived from CSdvObject. */ template class CSdvObjectClass : public ISdvObjectClassInfo { public: /** * @brief Constructor assigning the SDV prototype to the module. */ CSdvObjectClass() { // Add this object definition to the definition list. GetModule().ExposeObjectClass(this); } CSdvObjectClass(CSdvObjectClass&) = delete; CSdvObjectClass(CSdvObjectClass&&) = delete; CSdvObjectClass& operator=(CSdvObjectClass&) = delete; CSdvObjectClass& operator=(CSdvObjectClass&&) = delete; /** * @brief Destructor revoking the object prototype. */ virtual ~CSdvObjectClass() { GetModule().RevokeObjectClass(this); } /** * @brief Get the class information struct. Overload of ISdvObjectClassInfo::GetClassInfo. * @return Returns the class information struct. */ SClassInfo GetClassInfo() const override { SClassInfo sClassInfo{}; sClassInfo.ssName = GetClassName(); sClassInfo.seqClassAliases = GetClassAliases(); sClassInfo.ssDefaultObjectName = GetDefaultObjectName(); sClassInfo.ssDefaultConfig = GetDefaultConfig("Parameters"); sClassInfo.eType = GetObjectType(); sClassInfo.uiFlags = GetObjectFlags(); sClassInfo.seqDependencies = GetObjectDependencies(); return sClassInfo; } /** * @brief Get the object class name. Overload of ISdvObjectClassInfo::GetClassName. * @return Returns the class name of the object. * @remarks Default implementation gets the information from the object class if not defined in the definition. */ virtual u8string GetClassName() const override { return TSdvObject::GetClassNameStatic(); } /** * @brief Get the class name aliases. Overload of ISdvObjectClassInfo::GetClassAliases. * @return Returns a sequence containing zero or more class name aliases. * @remarks Default implementation gets the information from the object class if not defined in the definition. */ virtual sequence GetClassAliases() const override { return TSdvObject::GetClassAliasesStatic(); } /** * @brief Get the default config TOML string. Overload of ISdvObjectClassInfo::GetDefaultConfig. * @param[in] rssTablePrefix Reference to the string containing a table prefix to insert as part of the table key. * @return Returns the TOML string containing the default configuration of the object if exists. */ virtual u8string GetDefaultConfig(const u8string& rssTablePrefix /*= {}*/) const override { std::vector> vecParaminfo = TSdvObject::GetParamMapInfoStatic(); std::stringstream sstream; u8string ssGroup; for (const auto& ptrParam : vecParaminfo) { if (!ptrParam) continue; // Only describe readable and non-temporary parameters if (ptrParam->ReadOnly() || ptrParam->Temporary()) continue; // Is there a value? If not, skip the parameter if (ptrParam->DefaultVal().empty()) continue; // Need a new group? if (ptrParam->Group() != ssGroup) { if (sstream.rdbuf()->in_avail()) sstream << std::endl; sstream << "["; if (!rssTablePrefix.empty()) sstream << rssTablePrefix << "."; sstream << ptrParam->Group() << "]" << std::endl; ssGroup = ptrParam->Group(); } // Store the parameter // Quotation of parameter name needed? u8string ssName; if (ptrParam->Name().find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-") != u8string::npos) ssName = CObjectFactory::QuoteText(ptrParam->Name()); else ssName = ptrParam->Name(); sstream << ssName << " = "; if (ptrParam->Numeric() || ptrParam->Bitmask() || ptrParam->Enum()) { std::string ssValue = ptrParam->DefaultVal(); sstream << (ssValue.empty() ? "0" : ssValue); } else if (ptrParam->String()) sstream << CObjectFactory::QuoteText(ptrParam->DefaultVal()); else if (ptrParam->Boolean()) sstream << (ptrParam->DefaultVal().get() ? "true" : "false"); sstream << std::endl; } return sstream.str(); } /** * @brief Get the default object name. Overload of ISdvObjectClassInfo::GetDefaultObjectName. * @return Returns the default name of the object if exists. */ virtual u8string GetDefaultObjectName() const override { return TSdvObject::GetDefaultObjectNameStatic(); } /** * @brief Get the object type. Overload of ISdvObjectClassInfo::GetObjectType. * @return Returns the type of the object * @remarks Default implementation gets the information from the object class if not defined in the definition. */ virtual EObjectType GetObjectType() const override { return TSdvObject::GetObjectType(); } /** * @brief Get the object creation flags. Overload of ISdvObjectClassInfo::GetObjectFlags. * @return Returns the current flags of the object */ uint32_t GetObjectFlags() const override { uint32_t uiFlags = 0; if (TSdvObject::IsSingletonStatic()) uiFlags |= static_cast(EObjectFlags::singleton); // Currently no other flags defined. return uiFlags; } /** * @brief Get object dependencies. Overload of ISdvObjectClassInfo::GetObjectDependencies. * @return Returns a vector containing the class names of the objects this component is dependent on. */ virtual sequence GetObjectDependencies() const override { return TSdvObject::GetObjectDependenciesStatic(); } /** * @brief Create the SDV object. Overload of ISdvObjectClassInfo::CreateObject. * @return Returns an instance of the SDV object or nullptr when the object could not be created (exception thrown during construction). */ virtual std::unique_ptr CreateObject() override; }; /** * @brief SDV object base class. * @details The SDV object class implements all base functionality for is instantiating and controlling the SDV object. A SDV * object implementation can derive from this class and overload the following events to control object behavior: * - OnPreInitialize - prepare for initialization, no object configuration loaded yet. * - OnInitialize - initialize; object configuration is loaded and stored in the parameters. * - OnChangeToConfigMode - change to configuration mode; shutdown running instances. * - OnChangeToRunningMode - change to running mode, interpret configuration parameters and start running instances. * - OnShutdown - shutdown the object * - OnParamChanged - parameter has changed * - OnParamFlagChanged - parameter flag has changed */ class CSdvObject : public CSdvParamMap, public CSdvObjectAccess, public IObjectControl { public: /** * @brief Use the default object creator class. */ template using TSdvObjectCreator = CSdvObjectClass; /** * @brief Constructor */ CSdvObject() = default; CSdvObject(CSdvObject&) = delete; CSdvObject& operator=(CSdvObject&) = delete; CSdvObject(CSdvObject&&) = delete; CSdvObject& operator=(CSdvObject&&) = delete; /** * @brief Destructor */ ~CSdvObject() override {} /** * @brief Initialize the object. Overload of IObjectControl::Initialize. * @param[in] ssObjectConfig Optional configuration string. */ virtual void Initialize(/*in*/ const u8string& ssObjectConfig) override { // Not started before or ended completely? if (GetObjectState() != EObjectState::initialization_pending && GetObjectState() != EObjectState::destruction_pending) { m_eObjectState = EObjectState::runtime_error; return; } // Initializing m_eObjectState = EObjectState::initializing; // Initialize the parameter map. InitParamMap(); // Copy the configuration m_ssObjectConfig = ssObjectConfig; // Prepare initialization OnPreInitialize(); // Parse the object configuration if there is any. if (!ssObjectConfig.empty()) { try { // Parse the config TOML. toml::CTOMLParser parser(ssObjectConfig); if (!parser.IsValid()) { m_eObjectState = EObjectState::config_error; return; } // Read group function (iterative) u8string ssGroup; std::function fnReadGroup; fnReadGroup = [&](const toml::CNodeCollection& rTable) { // Iterate through the config. for (size_t n = 0; n < rTable.GetCount(); n++) { const toml::CNode node = rTable.Get(n); switch (node.GetType()) { case toml::ENodeType::node_integer: case toml::ENodeType::node_floating_point: case toml::ENodeType::node_boolean: case toml::ENodeType::node_string: // Ignore the result. If it is not possible to set the parameter, this is not a failure. SetParam(ssGroup + node.GetName(), node.GetValue()); break; case toml::ENodeType::node_table: { const toml::CNodeCollection sub_table= node; if (!sub_table.IsValid()) break; ssGroup += sub_table.GetName() + "."; fnReadGroup(sub_table); } default: // Other node types cannot be processed. break; } } }; // Parse the root node fnReadGroup(parser); } catch (const toml::XTOMLParseException& rexcept) { SDV_LOG(core::ELogSeverity::error, "Cannot process object configuration TOML: ", rexcept.what()); m_eObjectState = EObjectState::initialization_failure; return; } } // Let the object initialize itself. if (!OnInitialize()) { m_eObjectState = EObjectState::initialization_failure; return; } // Initialization is complete. m_eObjectState = EObjectState::initialized; } /** * @brief Get the current state of the object. Overload of IObjectControl::GetObjectState. * @return Return the current state of the object. */ virtual EObjectState GetObjectState() const override { return m_eObjectState; } /** * @brief Set the component operation mode. Overload of IObjectControl::SetOperationMode. * @param[in] eMode The operation mode, the component should run in. */ virtual void SetOperationMode(/*in*/ EOperationMode eMode) override { switch (eMode) { case EOperationMode::configuring: // Allowed? if (GetObjectState() != EObjectState::initialized && GetObjectState() != EObjectState::running && GetObjectState() != EObjectState::runtime_error && GetObjectState() != EObjectState::config_error) break; // Inform derived class OnChangeToConfigMode(); // Unlock the parameter map. UnlockParamMap(); m_eObjectState = EObjectState::configuring; break; case EOperationMode::running: // Allowed? if (GetObjectState() != EObjectState::initialized && GetObjectState() != EObjectState::configuring && GetObjectState() != EObjectState::config_error) break; // Lock the parameter map. LockParamMap(); // Inform the derived class if (!OnChangeToRunningMode()) { // Cannot enter running mode. Reconfiguration needed. m_eObjectState = EObjectState::config_error; UnlockParamMap(); break; } m_eObjectState = EObjectState::running; break; default: // Wrong operation mode. m_eObjectState = EObjectState::runtime_error; break; } } /** * @brief Get the object configuration for persistence. * @return The object configuration as TOML string. */ virtual u8string GetObjectConfig() const override { // During the initialization, return the stored object configuration. if (m_eObjectState == EObjectState::initializing || m_eObjectState == EObjectState::initialization_pending) return m_ssObjectConfig; // Split path function (splits the group from the parameter names) auto fnSplitPath = [](const u8string& rssPath) -> std::pair { size_t nPos = rssPath.find_last_of('.'); if (nPos == u8string::npos) return std::make_pair("", rssPath); return std::make_pair(rssPath.substr(0, nPos), rssPath.substr(nPos + 1)); }; // Create a new configuration string from the parameters. auto seqParameters = GetParamPaths(); if (seqParameters.empty()) return {}; // Iterate through the list of parameter names and create the TOML entries for it. u8string ssGroup; toml::CTOMLParser parser(""); toml::CNodeCollection table(parser); for (auto ssParamPath : seqParameters) { // Get the parameter object auto ptrParam = FindParamObject(ssParamPath); // Read only and temporary parameters are not stored if ((!ptrParam->Locked() && ptrParam->ReadOnly()) || !ptrParam->Temporary()) continue; // Get the value any_t anyValue = ptrParam->Get(); if (anyValue.empty()) continue; // Split the path in group and parameter name auto prParam = fnSplitPath(ssParamPath); // Need to add a group? if (prParam.first != ssGroup) { table = parser.AddTable(prParam.first); ssGroup = prParam.first; } // Store the value table.AddValue(prParam.second, anyValue); } return parser.GetTOML(); } /** * @brief Shutdown called before the object is destroyed. Overload of IObjectControl::Shutdown. * @attention Implement calls to other SDV objects here as this is no longer considered safe during the destructor of the * object! After a call to shutdown any threads/callbacks/etc that could call other SDV objects need to have been stopped. * The SDV object itself is to remain in a state where it can respond to calls to its interfaces as other objects may still * call it during the shutdown sequence! Any subsequent call to GetObjectState should return EObjectState::destruction_pending */ virtual void Shutdown() override { // Lock the parameter map - do not allow any more changes LockParamMap(); // Set status m_eObjectState = EObjectState::shutdown_in_progress; // Inform derived class OnShutdown(); // Set status m_eObjectState = EObjectState::destruction_pending; // Unlock parameter map to allow re-initialization. UnlockParamMap(); } /** * @brief Set the object into configuration error state. * @pre Works only when in configuration or initialized mode. * @post Reset by switching to configuration mode again or by trying to switch to running mode. */ void SetObjectIntoConfigErrorState() { if (m_eObjectState == EObjectState::initialized || m_eObjectState == EObjectState::configuring) m_eObjectState = EObjectState::config_error; } /** * @brief Set the onbject into runtime error state. * @pre Works only when in running mode. * @post Reset by switching to configuration mode. */ void SetObjectIntoRuntimeErrorState() { if (m_eObjectState == EObjectState::running) m_eObjectState = EObjectState::runtime_error; } /** * @brief Class aliases sequence if not provided. * @return An empty sequence of class aliases. */ static sequence GetClassAliasesStatic() { return {}; } /** * @brief Default object name is not set. * @return The empty default object name */ static u8string GetDefaultObjectNameStatic() { return {}; } /** * @brief Return whether the object is a singleton object (only one instance will be created of the object). * @return Returns whether the object is a singleton. */ static bool IsSingletonStatic() { return false; } /** * @brief Get object dependencies. * @return Returns a vector containing the class names of the objects this component is dependent on. */ static sequence GetObjectDependenciesStatic() { return sequence(); } /** * @brief Pre-initialization event, called before loading the object configuration. Overload this function to implement * pre-initialization functionality. */ virtual void OnPreInitialize() {} /** * @brief Initialization event, called after object configuration was loaded. Overload this function to implement object * initialization. * @return Returns 'true' when the initialization was successful, 'false' when not. */ virtual bool OnInitialize() { return true; } /** * @brief Change to configuration mode event. After this a call to this function locked parameters can be changed again. * Overload this function to shutdown any running instances and prepare for configuration. */ virtual void OnChangeToConfigMode() {} /** * @brief Change to running mode event. Parameters were locked before the call to this event. Overload this function to * interpret the configuration parameters and start the running instances. * @return Returns 'true' when the configuration is valid and the running instances could be started. Otherwise returns * 'false'. */ virtual bool OnChangeToRunningMode() { return true; } /** * @brief Shutdown the object. Overload this function to terminate any running instances and prepare the object for * termination or restarting. */ virtual void OnShutdown() {} /** * @brief Interface map */ BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_ENTRY(IParameters) SDV_INTERFACE_ENTRY(IObjectControl) END_SDV_INTERFACE_MAP() private: std::atomic m_eObjectState = EObjectState::initialization_pending; ///< Object status std::string m_ssObjectConfig; ///< Copy of the configuration TOML. }; /** * @brief The object control wrapper class ensures, that the CSdvObject interface map is always accessible. * @tparam The class to instantiate. */ template class CObjectInstance : public TSdvObject { static_assert(std::is_base_of_v, "TSdvObject must derive from sdv::CSdvObject!"); public: // Interface map BEGIN_SDV_INTERFACE_MAP() SDV_INTERFACE_CHAIN_BASE(CSdvObject) SDV_INTERFACE_CHAIN_BASE(TSdvObject) END_SDV_INTERFACE_MAP() }; ///////////////////////////////////////// // Object control class implementation // ///////////////////////////////////////// inline sequence CObjectFactory::GetClassNames() const { std::shared_lock lock(m_mtxObjectClasses); sequence seqClassNames; for (const ISdvObjectClassInfo* pClassInfo : m_vecObjectClasses) { if (!pClassInfo) continue; seqClassNames.push_back(pClassInfo->GetClassName()); } return seqClassNames; } inline SClassInfo CObjectFactory::GetClassInfo(const u8string& ssClassName) const { if (ssClassName.empty()) return {}; std::shared_lock lock(m_mtxObjectClasses); for (ISdvObjectClassInfo* pClassInfo : m_vecObjectClasses) { if (!pClassInfo) continue; // Check for the class name. bool bFound = pClassInfo->GetClassName() == ssClassName; // If not found, check for all aliases. auto seqClassAliases = pClassInfo->GetClassAliases(); for (auto itAlias = seqClassAliases.begin(); !bFound && itAlias != seqClassAliases.end(); ++itAlias) bFound = *itAlias == ssClassName; if (!bFound) continue; // Return the class information return pClassInfo->GetClassInfo(); } // No class information found return {}; } inline IInterfaceAccess* CObjectFactory::CreateObject(const u8string& ssClassName) { if (ssClassName.empty()) return nullptr; std::shared_lock lock(m_mtxObjectClasses); for (ISdvObjectClassInfo* pClassInfo : m_vecObjectClasses) { if (!pClassInfo) continue; // Check for the class name. bool bFound = pClassInfo->GetClassName() == ssClassName; // If not found, check for all aliases. auto seqClassAliases = pClassInfo->GetClassAliases(); for (auto itAlias = seqClassAliases.begin(); !bFound && itAlias != seqClassAliases.end(); ++itAlias) bFound = *itAlias == ssClassName; if (!bFound) continue; ++m_uiActiveObjectCount; auto object = pClassInfo->CreateObject(); lock.unlock(); if(!object) { --m_uiActiveObjectCount; return nullptr; } auto ret = object.get()->GetObjectAccess(); std::unique_lock lockObjects (m_mtxActiveObjects); m_vecActiveObjects.emplace_back(std::move(object)); return ret; } return nullptr; } inline void CObjectFactory::DestroyObject(IInterfaceAccess* object) { if (object == nullptr) { return; } std::unique_lock lockObjects(m_mtxActiveObjects); for(auto iter = m_vecActiveObjects.begin(); iter != m_vecActiveObjects.end(); ++iter) { if(iter->get()->GetObjectAccess() == object) { auto objectPtr = std::move(*iter); m_vecActiveObjects.erase(iter); lockObjects.unlock(); objectPtr = nullptr; if (m_uiActiveObjectCount) --m_uiActiveObjectCount; return; } } } inline void CObjectFactory::DestroyAllObjects() { std::unique_lock lockObjects(m_mtxActiveObjects); auto objects = std::move(m_vecActiveObjects); lockObjects.unlock(); while(!objects.empty()) { objects.pop_back(); if (m_uiActiveObjectCount) --m_uiActiveObjectCount; } } inline void CObjectFactory::BuildManifest() { // Start the manifest std::stringstream sstream; sstream << R"toml(# Module manifest [Interface] Version = )toml" << SDVFrameworkInterfaceVersion << std::endl << std::endl; // Add all classes to the manifest std::shared_lock lock(m_mtxObjectClasses); for (const ISdvObjectClassInfo* pClassInfo : m_vecObjectClasses) { sstream << "[[Class]]" << std::endl; sstream << "Name = " << QuoteText(pClassInfo->GetClassName()) << std::endl; if (pClassInfo->GetClassName().empty()) continue; if (!pClassInfo->GetClassAliases().empty()) { sstream << "Aliases = ["; bool bInitialAlias = true; for (const u8string& rssAlias : pClassInfo->GetClassAliases()) { if (!bInitialAlias) sstream << ", "; bInitialAlias = false; sstream << QuoteText(rssAlias); } sstream << "]" << std::endl; } if (!pClassInfo->GetDefaultObjectName().empty()) sstream << "DefaultName = " << QuoteText(pClassInfo->GetDefaultObjectName()) << std::endl; if (pClassInfo->GetObjectType() == EObjectType::undefined) continue; sstream << "Type = " << QuoteText(ObjectType2String(pClassInfo->GetObjectType())) << std::endl; if (pClassInfo->GetObjectFlags() & static_cast(EObjectFlags::singleton)) sstream << "Singleton = true" << std::endl; if (!pClassInfo->GetObjectDependencies().empty()) { sstream << "Dependencies = ["; bool bInitialDependency = true; for (const u8string& rssDependsOn : pClassInfo->GetObjectDependencies()) { if (!bInitialDependency) sstream << ", "; bInitialDependency = false; sstream << "\"" << rssDependsOn << "\""; } sstream << "]" << std::endl; } // Assign the parameter table to the class. Prepend [Class.Parameters] to every table. std::string ssParameters = pClassInfo->GetDefaultConfig("Class.Parameters"); if (!ssParameters.empty()) sstream << ssParameters; } m_ssManifest = sstream.str(); } inline void CObjectFactory::ExposeObjectClass(ISdvObjectClassInfo* pClassInfo) { if (pClassInfo) { std::unique_lock lock(m_mtxObjectClasses); m_vecObjectClasses.push_back(pClassInfo); // Attention: pObjectClass is a pointer to ISdvObjectClassInfo even if the class was derived. // Virtual functions are not available yet at this stage. } } inline void CObjectFactory::RevokeObjectClass(const ISdvObjectClassInfo* pClassInfo) { std::unique_lock lock(m_mtxObjectClasses); auto itObjectClass = std::find(m_vecObjectClasses.begin(), m_vecObjectClasses.end(), pClassInfo); if (itObjectClass != m_vecObjectClasses.end()) m_vecObjectClasses.erase(itObjectClass); // TODO EVE: Updated through cppcheck warning //for (auto objectClassIter = m_vecObjectClasses.begin(); objectClassIter != m_vecObjectClasses.end(); // objectClassIter++) //{ // if (*objectClassIter == pClassInfo) // { // m_vecObjectClasses.erase(objectClassIter); // break; // } //} } inline std::string CObjectFactory::QuoteText(const std::string& rssText) { std::stringstream sstreamQuotedText; sstreamQuotedText << "\""; for (size_t nPos = 0; nPos < rssText.size(); nPos++) { uint8_t uiChar = static_cast(rssText[nPos]); uint32_t uiUTFChar = 0; switch (uiChar) { case '\'': sstreamQuotedText << '\''; break; // Single quote character case '\b': sstreamQuotedText << "\\b"; break; // Escape backspace case '\t': sstreamQuotedText << "\\t"; break; // Escape tab case '\n': sstreamQuotedText << "\\n"; break; // Escape linefeed case '\f': sstreamQuotedText << "\\f"; break; // Escape form feed case '\r': sstreamQuotedText << "\\r"; break; // Escape carriage return case '\"': sstreamQuotedText << "\\\""; break; // Escape quote case '\\': sstreamQuotedText << "\\\\"; break; // Escape backslash default: // Check for ASCII character if (uiChar >= 0x20 && uiChar < 0x7f) { // Standard ASCII sstreamQuotedText << static_cast(uiChar); break; } // Use UNICODE escape character for the quoted text if (uiChar <= 0x80) // One byte UTF-8 uiUTFChar = static_cast(uiChar); else if (uiChar <= 0xDF) // Two bytes UTF-8 { uiUTFChar = static_cast(uiChar & 0b00011111) << 6; // Expecting the next character to be between 0x80 and 0xBF nPos++; if (nPos >= rssText.size()) break; uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); } else if (uiChar <= 0xEF) // Three bytes UTF-8 { uiUTFChar = static_cast(uiChar & 0b00001111) << 6; // Expecting the next character to be between 0x80 and 0xBF nPos++; if (nPos >= rssText.size()) break; uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); uiUTFChar <<= 6; // Expecting the next character to be between 0x80 and 0xBF nPos++; if (nPos >= rssText.size()) break; uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); } else if (uiChar <= 0xF7) // Four bytes UTF-8 { uiUTFChar = static_cast(uiChar & 0b00000111) << 6; // Expecting the next character to be between 0x80 and 0xBF nPos++; if (nPos >= rssText.size()) break; uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); uiUTFChar <<= 6; // Expecting the next character to be between 0x80 and 0xBF nPos++; if (nPos >= rssText.size()) break; uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); uiUTFChar <<= 6; // Expecting the next character to be between 0x80 and 0xBF nPos++; if (nPos >= rssText.size()) break; uiUTFChar |= static_cast(rssText[nPos] & 0b00111111); } // Stream the UTF character if (uiUTFChar <= 0xFFFF) sstreamQuotedText << "\\u" << std::uppercase << std::hex << std::setfill('0') << std::setw(4) << uiUTFChar; else sstreamQuotedText << "\\U" << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << uiUTFChar; break; } } sstreamQuotedText << "\""; return sstreamQuotedText.str(); } template inline std::unique_ptr CSdvObjectClass::CreateObject() { std::unique_ptr ret; try { ret = std::make_unique>(); } catch (...) { SDV_LOG(core::ELogSeverity::error, "Failed to instantiate object of class ", GetClassName(), " - exception thrown during construction! "); } return ret; } } // namespace sdv // NOLINTBEGIN(cppcoreguidelines-macro-usage) #ifdef SDV_NO_CLASS_DEFINITION /** * @brief Define the SDV component access functions, but do not define the SDV object. * @param sdv_object_class The object class must derive from sdv::CSdvObject. */ #define DEFINE_SDV_OBJECT(sdv_object_class) \ extern "C" bool HasActiveObjects(); \ extern "C" sdv::IInterfaceAccess* GetModuleFactory(uint32_t uiInterfaceVersion); \ extern "C" const char* GetManifest(); #elif defined SDV_NO_EXPORT_DEFINITION /** * @brief Define the SDV object (derived from CSdvObject) to be exposed to the framework. Do not define export functions. * @param sdv_object_class The object class must derive from sdv::CSdvObject. */ #define DEFINE_SDV_OBJECT(sdv_object_class) \ /** \ * @brief Object instantiation helper class. \ */ \ struct SObjectClassInstance_##sdv_object_class \ { \ public: \ /** \ * @brief Constructor \ */ \ SObjectClassInstance_##sdv_object_class() \ { \ /* Enforce derivation of sdv::CSdvObject. */ \ static_assert(std::is_base_of::value, \ "CSdvObject is not base of sdv_object_class"); \ /* Call the static function once to instantiate the definition. */ \ GetObjectClassInstance(); \ } \ /** \ * @brief Get the creator class for the object instance. \ * @return The global creator class. \ */ \ static sdv_object_class::TSdvObjectCreator& GetObjectClassInstance() \ { \ static sdv_object_class::TSdvObjectCreator object_class; \ return object_class; \ } \ }; \ /** Global instance of the object helper class. */ \ static SObjectClassInstance_##sdv_object_class g_##sdv_object_class; \ bool HasActiveObjects(); \ sdv::IInterfaceAccess* GetModuleFactory(uint32_t uiInterfaceVersion); \ const char* GetManifest(); #else /** * @brief Define the SDV object (derived from CSdvObject) to be exposed to the framework. * @param sdv_object_class The object class must derive from sdv::CSdvObject. */ #define DEFINE_SDV_OBJECT(sdv_object_class) \ /** \ * @brief Object instantiation helper class. \ */ \ struct SObjectClassInstance_##sdv_object_class \ { \ public: \ /** \ * @brief Constructor \ */ \ SObjectClassInstance_##sdv_object_class() \ { \ /* Enforce derivation of sdv::CSdvObject. */ \ static_assert(std::is_base_of::value, \ "CSdvObject is not base of sdv_object_class"); \ /* Call the static function once to instantiate the definition. */ \ GetObjectClassInstance(); \ } \ /** \ * @brief Get the creator class for the object instance. \ * @return The global creator class. \ */ \ static sdv_object_class::TSdvObjectCreator& GetObjectClassInstance() \ { \ static sdv_object_class::TSdvObjectCreator object_class; \ return object_class; \ } \ }; \ /** Global instance of the object helper class. */ \ static SObjectClassInstance_##sdv_object_class g_##sdv_object_class; \ extern "C" SDV_SYMBOL_PUBLIC bool HasActiveObjects(); \ extern "C" SDV_SYMBOL_PUBLIC sdv::IInterfaceAccess* GetModuleFactory(uint32_t uiInterfaceVersion); \ extern "C" SDV_SYMBOL_PUBLIC const char* GetManifest(); #endif // !defined SDV_NO_CLASS_DEFINITION && !defined SDV_NO_EXPORT_DEFINITION /** * @brief Declare the object class type. To be placed in the SDV object class derived from CSdvObject. * @param class_type The type of the object (EObjectType). */ #define DECLARE_OBJECT_CLASS_TYPE(class_type) \ /** \ * @brief Get the object class type. \ * @return Returns the type of the object (EObjectType). \ */ \ constexpr static sdv::EObjectType GetObjectType() \ { \ return class_type; \ } /** * @brief Declare the object class name. To be placed in the SDV object class derived from CSdvObject. * @param class_name_string The UTF-8 string containing the name of the object. */ #define DECLARE_OBJECT_CLASS_NAME(class_name_string) \ /** \ * @brief Get the class name. \ * @return Returns the string containing the class name. \ */ \ static sdv::u8string GetClassNameStatic() \ { \ return class_name_string; \ } /** * @brief Declare the object class aliases. To be placed in the SDV object class derived from CSdvObject. * @param ... Multiple UTF-8 strings containing the alias names for the object class. */ #define DECLARE_OBJECT_CLASS_ALIAS(...) \ /** \ * @brief Get the list of class name aliases. \ * @return Vector containing the class name alias strings. \ */ \ static sdv::sequence GetClassAliasesStatic() \ { \ return sdv::sequence({__VA_ARGS__}); \ } /** * @brief Declare the default object name. To be placed in the SDV object class derived from CSdvObject. * @param object_name_string The UTF-8 string containing the name of the object. */ #define DECLARE_DEFAULT_OBJECT_NAME(object_name_string) \ /** \ * @brief Get the default name to be used during the object instantiation. \ * @return String containing the default name. \ */ \ static sdv::u8string GetDefaultObjectNameStatic() \ { \ return object_name_string; \ } /** * @brief Declare the object to be a singleton object (only one instance will be created of the object). */ #define DECLARE_OBJECT_SINGLETON() \ /** \ * @brief Is the class defined as singleton (allows at the most one instance of the class)? \ * @return Returns whether the class is defined as singleton. \ */ \ static bool IsSingletonStatic() { return true; } /** * @brief Declare the object to be a singleton object (only one instance will be created of the object). * One or more strings with the class/default names of the objects the component is dependent on. */ #define DECLARE_OBJECT_DEPENDENCIES(...) \ /** \ * @brief Get the list of object dependencies. \ * @return The sequence containing the dependency strings. \ */ \ static sdv::sequence GetObjectDependenciesStatic() { return sdv::sequence({__VA_ARGS__}); } // NOLINTEND(cppcoreguidelines-macro-usage) /* * @brief Returns whether or not instances of objects implemented by this module are running. If none, the module can be unloaded. * @remarks Unloading the module with running instances could cause a crash and should be prevented at all costs. Unloading the * module removes the code of the objects still running. * @return Returns true when object instances are running; otherwise returns 'false'. */ inline extern bool HasActiveObjects() { return sdv::GetModule().GetActiveObjects() != 0; } /* * @brief Get the module factory interface with a specific version. * @details This function provides access to the objects being implemented in this module. * @param[in] interfaceVersion Request the module factory for a specific interface version. Using another interface version than * the one returned from the manifest, might cause the function to fail. * @return Returns pointer to the IInterfaceAccess interface of the module factory object. */ inline extern sdv::IInterfaceAccess* GetModuleFactory(uint32_t interfaceVersion) { return sdv::GetModule().GetModuleControl(interfaceVersion); } /* * @brief Get the the module manifest. * @details Each module contains a manifest containing general information as well as information about the component classes. This * allows installing the component without having to instantiate the classes. * @return Returns the pointer to a zero terminated string containing the module manifest or NULL when there is no string. */ inline extern const char* GetManifest() { return sdv::GetModule().GetManifest(); } #endif // !defined COMPONENT_IMPL_H