Precommit (#1)

* first commit

* cleanup
This commit is contained in:
tompzf
2025-11-04 13:28:06 +01:00
committed by GitHub
parent dba45dc636
commit 6ed4b1534e
898 changed files with 256340 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
# Define project
project(sdv_service_datadispatchservice VERSION 1.0 LANGUAGES CXX)
# Define target
add_library(data_dispatch_service SHARED
"dispatchservice.cpp"
"dispatchservice.h"
"transaction.cpp"
"transaction.h"
"signal.cpp"
"signal.h" "trigger.h" "trigger.cpp")
target_link_libraries(data_dispatch_service ${CMAKE_THREAD_LIBS_INIT})
target_link_options(data_dispatch_service PRIVATE)
target_include_directories(data_dispatch_service PRIVATE ./include/)
set_target_properties(data_dispatch_service PROPERTIES PREFIX "")
set_target_properties(data_dispatch_service PROPERTIES SUFFIX ".sdv")
# Build dependencies
add_dependencies(data_dispatch_service CompileCoreIDL)
# Appending the service in the service list
set(SDV_Service_List ${SDV_Service_List} data_dispatch_service PARENT_SCOPE)

View File

@@ -0,0 +1,215 @@
#include "dispatchservice.h"
CDispatchService::CDispatchService()
{
CreateDirectTransactionID();
}
sdv::IInterfaceAccess* CDispatchService::CreateTxTrigger(uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
sdv::IInterfaceAccess* pTriggerCallback)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
// Check for parameter validity
if (!uiCycleTime && !(uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
return nullptr;
if (!pTriggerCallback)
return nullptr;
sdv::core::ITxTriggerCallback* pCallback = pTriggerCallback->GetInterface<sdv::core::ITxTriggerCallback>();
if (!pCallback) return nullptr;
std::unique_ptr<CTrigger> ptrTrigger = std::make_unique<CTrigger>(*this, uiCycleTime, uiDelayTime, uiBehaviorFlags, pCallback);
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
// exception was triggered).
// cppcheck-suppress knownConditionTrueFalse
if (!ptrTrigger)
return nullptr;
CTrigger* pObject = ptrTrigger.get();
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
// exception was triggered).
// cppcheck-suppress knownConditionTrueFalse
if (!pObject)
return nullptr;
if (!ptrTrigger->IsValid()) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxTriggers);
return m_mapTriggers.emplace(pObject, std::move(ptrTrigger)).second ? pObject : nullptr;
}
sdv::IInterfaceAccess* CDispatchService::RegisterTxSignal(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::any_t anyDefVal)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto prSignal = m_mapTxSignals.try_emplace(ssSignalName, *this, ssSignalName, sdv::core::ESignalDirection::sigdir_tx, anyDefVal);
return prSignal.first->second.CreateConsumer();
}
sdv::IInterfaceAccess* CDispatchService::RegisterRxSignal(/*in*/ const sdv::u8string& ssSignalName)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto prSignal = m_mapRxSignals.try_emplace(ssSignalName, *this, ssSignalName, sdv::core::ESignalDirection::sigdir_rx);
return prSignal.first->second.CreateProvider();
}
sdv::IInterfaceAccess* CDispatchService::RequestSignalPublisher(/*in*/ const sdv::u8string& ssSignalName)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto itSignal = m_mapTxSignals.find(ssSignalName);
if (itSignal == m_mapTxSignals.end()) return nullptr;
if (itSignal->second.GetDirection() != sdv::core::ESignalDirection::sigdir_tx)
return nullptr;
return itSignal->second.CreateProvider();
}
sdv::IInterfaceAccess* CDispatchService::AddSignalSubscription(/*in*/ const sdv::u8string& ssSignalName, /*in*/ IInterfaceAccess* pSubscriber)
{
if (m_eObjectStatus != sdv::EObjectStatus::configuring) return nullptr;
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto itSignal = m_mapRxSignals.find(ssSignalName);
if (itSignal == m_mapRxSignals.end()) return 0;
if (itSignal->second.GetDirection() != sdv::core::ESignalDirection::sigdir_rx)
return 0ull;
return itSignal->second.CreateConsumer(pSubscriber);
}
sdv::sequence<sdv::core::SSignalRegistration> CDispatchService::GetRegisteredSignals() const
{
sdv::sequence<sdv::core::SSignalRegistration> seqRegistrations;
std::unique_lock<std::mutex> lock(m_mtxSignals);
for (const auto& prSignal : m_mapRxSignals)
seqRegistrations.push_back({prSignal.first, prSignal.second.GetDirection()});
for (const auto& prSignal : m_mapTxSignals)
seqRegistrations.push_back({prSignal.first, prSignal.second.GetDirection()});
return seqRegistrations;
}
sdv::IInterfaceAccess* CDispatchService::CreateTransaction()
{
// NOTE: Transactions can take place at any time (not only when running). DO not restrict transactions to any
// operation mode.
std::unique_lock<std::mutex> lock(m_mtxTransactions);
auto itTransaction = m_lstTransactions.emplace(m_lstTransactions.end(), *this);
if (itTransaction == m_lstTransactions.end()) return nullptr;
itTransaction->SetIterator(itTransaction);
return &(*itTransaction);
}
uint64_t CDispatchService::GetNextTransactionID()
{
return m_uiTransactionCnt++;
}
void CDispatchService::CreateDirectTransactionID()
{
m_uiDirectTransactionID = GetNextTransactionID();
}
uint64_t CDispatchService::GetDirectTransactionID() const
{
return m_uiDirectTransactionID;
}
void CDispatchService::Initialize(const sdv::u8string& /*ssObjectConfig*/)
{
m_eObjectStatus = sdv::EObjectStatus::initializing;
m_scheduler.Start();
m_eObjectStatus = sdv::EObjectStatus::initialized;
}
sdv::EObjectStatus CDispatchService::GetStatus() const
{
return m_eObjectStatus;
}
void CDispatchService::SetOperationMode(sdv::EOperationMode eMode)
{
switch (eMode)
{
case sdv::EOperationMode::configuring:
if (m_eObjectStatus == sdv::EObjectStatus::running || m_eObjectStatus == sdv::EObjectStatus::initialized)
m_eObjectStatus = sdv::EObjectStatus::configuring;
break;
case sdv::EOperationMode::running:
if (m_eObjectStatus == sdv::EObjectStatus::configuring || m_eObjectStatus == sdv::EObjectStatus::initialized)
m_eObjectStatus = sdv::EObjectStatus::running;
break;
default:
break;
}
}
void CDispatchService::Shutdown()
{
m_eObjectStatus = sdv::EObjectStatus::shutdown_in_progress;
m_scheduler.Stop();
m_eObjectStatus = sdv::EObjectStatus::initialization_pending;
}
void CDispatchService::UnregisterSignal(/*in*/ const sdv::u8string& ssSignalName, sdv::core::ESignalDirection eDirection)
{
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
// when running.
std::unique_lock<std::mutex> lock(m_mtxSignals);
// Remove the signal
if (eDirection == sdv::core::ESignalDirection::sigdir_rx)
m_mapRxSignals.erase(ssSignalName);
else
m_mapTxSignals.erase(ssSignalName);
}
CSignal* CDispatchService::FindSignal(const sdv::u8string& rssSignalName, sdv::core::ESignalDirection eDirection)
{
std::unique_lock<std::mutex> lock(m_mtxSignals);
if (eDirection == sdv::core::ESignalDirection::sigdir_rx)
{
auto itSignal = m_mapRxSignals.find(rssSignalName);
return itSignal == m_mapRxSignals.end() ? nullptr : &itSignal->second;
} else
{
auto itSignal = m_mapTxSignals.find(rssSignalName);
return itSignal == m_mapTxSignals.end() ? nullptr : &itSignal->second;
}
}
void CDispatchService::FinishTransaction(const CTransaction* pTransaction)
{
// NOTE: Transactions can take place at any time (not only when running). DO not restrict transactions to any
// operation mode.
std::unique_lock<std::mutex> lock(m_mtxTransactions);
// Delete the transaction
auto itTransaction = pTransaction->GetIterator();
m_lstTransactions.erase(itTransaction);
}
CScheduler& CDispatchService::GetScheduler()
{
return m_scheduler;
}
void CDispatchService::RemoveTxTrigger(CTrigger* pTrigger)
{
// NOTE: Normally the remove function should be called in the configuration mode. Since it doesn't give
// feedback and the associated caller might delete any receiving function, allow the removal to take place even
// when running.
std::unique_lock<std::mutex> lock(m_mtxTriggers);
m_scheduler.RemoveFromSchedule(pTrigger);
m_mapTriggers.erase(pTrigger);
}

View File

@@ -0,0 +1,220 @@
#ifndef DISPATCH_SERVICE_H
#define DISPATCH_SERVICE_H
#include <interfaces/dispatch.h>
#include <support/component_impl.h>
#include <memory>
#include <map>
#include <list>
#include <set>
// Data dispatch service for CAN:
//
// - CAN Link object for Lotus Eletre registers all the CAN signals as follows:
// - Rx Signals are being registered to receive data from the vehicle and supply data to the vehicle devices.
// - Signals are all grouped per message and per node/ECU
// - One event is sent to update the data in the vehicle devices
// - Data is updated first and then the event is broadcasted (toggle buffer)
// - Tx Signals are being registered to receive data from the vehicle devices and supply data to the vehicle
// - Signals are grouped per message and per node/ECU
// - (Default values are available for each signal; either one time reset or reset after each send) -> service task?
// - Sending per event or per timer
#include "transaction.h"
#include "signal.h"
#include "trigger.h"
/**
* @brief data dispatch service to read/write and react on signal changes
*/
class CDispatchService : public sdv::CSdvObject, public sdv::core::ISignalTransmission, public sdv::core::ISignalAccess,
public sdv::core::IDispatchTransaction, public sdv::IObjectControl
{
public:
/**
* @brief Constructor
*/
CDispatchService();
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::core::ISignalTransmission)
SDV_INTERFACE_ENTRY(sdv::core::ISignalAccess)
SDV_INTERFACE_ENTRY(sdv::core::IDispatchTransaction)
SDV_INTERFACE_ENTRY(sdv::IObjectControl)
END_SDV_INTERFACE_MAP()
// Object declarations
DECLARE_OBJECT_CLASS_TYPE(sdv::EObjectType::SystemObject)
DECLARE_OBJECT_CLASS_NAME("DataDispatchService")
DECLARE_OBJECT_SINGLETON()
DECLARE_OBJECT_DEPENDENCIES("TaskTimerService")
/**
* @brief Create a TX trigger object that defines how to trigger the signal transmission. Overload of
* sdv::core::ISignalTransmission::CreateTxTrigger.
* @param[in] uiCycleTime When set to any value other than 0, provides a cyclic trigger (ms). Could be 0 if cyclic
* triggering is not required.
* @param[in] uiDelayTime When set to any value other than 0, ensures a minimum time between two triggers. Could be 0
* if minimum time should not be enforced.
* @param[in] uiBehaviorFlags Zero or more flags from sdv::core::ETxTriggerBehavior.
* @param[in] pTriggerCallback Pointer to the trigger callback object. This object needs to expose the ITxTriggerCallback
* interface. This interface must stay valid during the lifetime of the generated trigger object.
* @returns On success, returns an interface to the trigger object. Use the ITxTrigger interface to assign signals to
* the trigger. Returns null when a trigger was requested without cycletime and without trigger behavior (which would
* mean it would never trigger). Use IObjectDestroy to destroy the trigger object.
*/
IInterfaceAccess* CreateTxTrigger(uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
sdv::IInterfaceAccess* pTriggerCallback);
/**
* @brief Register a signal for sending over the network; reading from the dispatch service. Data is provided by the
* signal publisher and dependable on the requested behavior stored until it is sent. Overload of
* sdv::core::ISignalTransmission::RegisterTxSignal.
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
* @param[in] anyDefVal The default value of the signal.
* @return Returns the IInterfaceAccess interface that allows access to the ISignalRead interface for reading the
* signal value.
*/
virtual sdv::IInterfaceAccess* RegisterTxSignal(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::any_t anyDefVal) override;
/**
* @brief Register a signal for reception over the network; providing to the dispatch service. Overload of
* sdv::core::ISignalTransmission::RegisterRxSignal.
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
* @return Returns the IInterfaceAccess interface that allows access to the ISignalWrite interface for writing the
* signal value.
*/
virtual sdv::IInterfaceAccess* RegisterRxSignal(/*in*/ const sdv::u8string& ssSignalName) override;
/**
* @brief Requested a registered signal for publication (send signal). Overload of
* sdv::core::ISignalAccess::RequestSignalPublisher.
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
* @return Returns the IInterfaceAccess interface that allows access to the ISignalWrite interface for writing the
* signal value.
*/
virtual sdv::IInterfaceAccess* RequestSignalPublisher(/*in*/ const sdv::u8string& ssSignalName) override;
/**
* @brief Add a registered signal for subscription (receive signal). Overload of
* sdv::core::ISignalAccess::AddSignalSubscription.
* @param[in] ssSignalName Name of the signal. To guarantee uniqueness, it is preferred to add the group hierarchy to the
* signal name separated by a dot. E.g. with CAN: MAB.BcmChas1Fr03.SteerReCtrlReqAgReq
* @param[in] pSubscriber Pointer to the IInterfaceAccess of the subscriber. The subscriber should implement the
* ISignalReceiveEvent interface.
* @return Returns an interface that can be used to manage the subscription. Use IObjectDestroy to destroy the signal object.
*/
virtual sdv::IInterfaceAccess* AddSignalSubscription(/*in*/ const sdv::u8string& ssSignalName, /*in*/ sdv::IInterfaceAccess* pSubscriber) override;
/**
* @brief Get a list of registered signals.
* @return List of registration functions.
*/
virtual sdv::sequence<sdv::core::SSignalRegistration> GetRegisteredSignals() const override;
/**
* @brief CreateTransaction a transaction. Overload of sdv::core::IDispatchTransaction::CreateTransaction.
* @details When starting a group transaction, any writing to a signal will not be reflected yet until the transaction
* is finalized. For the data link layer, this also allows freezing the reading values until all values have been read.
* @return Returns the transaction interface or NULL when the transaction could not be started. Use IObjectDestroy to
* destroy the transaction object.
*/
virtual sdv::IInterfaceAccess* CreateTransaction() override;
/**
* @brief Get the next transaction ID.
* @return Returns the next transaction ID.
*/
uint64_t GetNextTransactionID();
/**
* @brief Create a new direct transaction ID.
* @details The read transaction ID is used fir direct transmission after a read transaction was started. This prevents direct
* transmission overwriting the protected read transaction.
*/
void CreateDirectTransactionID();
/**
* @brief Get the current direct transaction ID.
* @return The transaction ID.
*/
uint64_t GetDirectTransactionID() const;
/**
* @brief Initialize the object. Overload of sdv::IObjectControl::Initialize.
* @param[in] ssObjectConfig Optional configuration string.
*/
void Initialize(const sdv::u8string& ssObjectConfig) override;
/**
* @brief Get the current status of the object. Overload of sdv::IObjectControl::GetStatus.
* @return Return the current status of the object.
*/
sdv::EObjectStatus GetStatus() const override;
/**
* @brief Set the component operation mode. Overload of sdv::IObjectControl::SetOperationMode.
* @param[in] eMode The operation mode, the component should run in.
*/
void SetOperationMode(sdv::EOperationMode eMode) override;
/**
* @brief Shutdown called before the object is destroyed. Overload of sdv::IObjectControl::Shutdown.
*/
void Shutdown() override;
/**
* @brief Unregister a previously registered signal. This will render all subscriptions and provider connections invalid.
* @param[in] ssSignalName Name of the signal to unregister.
* @param[in] eDirection The signal direction determines from which map the signal should be unregistered.
*/
void UnregisterSignal(const sdv::u8string& ssSignalName, sdv::core::ESignalDirection eDirection);
/**
* @brief Find the signal with the supplied name.
* @param[in] rssSignalName Name of the signal to find.
* @param[in] eDirection The signal direction determines at which map the signal should be searched for.
* @return Pointer to the signal or NULL when the signal could not be found.
*/
CSignal* FindSignal(const sdv::u8string& rssSignalName, sdv::core::ESignalDirection eDirection);
/**
* @brief Finalize a transaction transaction. Any update made on this interface between the start and the finalize will be
* in effect at once.
* @param[in] pTransaction The transaction to finalize.
*/
void FinishTransaction(const CTransaction* pTransaction);
/**
* @brief Get the trigger execution scheduler.
* @return Returns a reference to the contained trigger execution scheduler.
*/
CScheduler& GetScheduler();
/**
* @brief Remove a trigger object from the trigger map.
* @param[in] pTrigger Pointer to the trigger object to remove.
*/
void RemoveTxTrigger(CTrigger* pTrigger);
private:
mutable std::mutex m_mtxSignals; ///< Signal object map protection.
std::map<sdv::u8string, CSignal> m_mapRxSignals; ///< Signal object map.
std::map<sdv::u8string, CSignal> m_mapTxSignals; ///< Signal object map.
sdv::EObjectStatus m_eObjectStatus = sdv::EObjectStatus::initialization_pending; ///< Object status.
std::atomic_uint64_t m_uiTransactionCnt = 1ull; ///< Transaction counter.
std::mutex m_mtxTransactions; ///< List with transactions access.
std::list<CTransaction> m_lstTransactions; ///< List with transactions.
uint64_t m_uiDirectTransactionID; ///< Current direct transaction ID.
CScheduler m_scheduler; ///< Scheduler for trigger execution.
mutable std::mutex m_mtxTriggers; ///< Trigger object map protection.
std::map<CTrigger*, std::unique_ptr<CTrigger>> m_mapTriggers; ///< Trigger object map.
};
DEFINE_SDV_OBJECT(CDispatchService)
#endif // !defined DISPATCH_SERVICE_H

View File

@@ -0,0 +1,246 @@
#include "dispatchservice.h"
#include "signal.h"
#include "trigger.h"
#include "transaction.h"
CProvider::CProvider(CSignal& rSignal) : m_rSignal(rSignal)
{}
void CProvider::DestroyObject()
{
m_rSignal.RemoveProvider(this);
}
void CProvider::Write(/*in*/ sdv::any_t anyVal, /*in*/ sdv::IInterfaceAccess* pTransaction)
{
// Let the transaction handle the write if there is a transaction.
CTransaction* pTransactionObj = static_cast<CTransaction*>(pTransaction);
if (pTransactionObj)
{
pTransactionObj->DeferWrite(m_rSignal, anyVal);
return;
}
// Write the value
// Note, explicitly convert to uint64_t to resolve ambiguous function selection under Linux.
std::set<CTrigger*> setTriggers;
m_rSignal.WriteFromProvider(anyVal, static_cast<uint64_t>(0), setTriggers);
// Execute the triggers
for (CTrigger* pTrigger : setTriggers)
pTrigger->Execute();
}
CConsumer::CConsumer(CSignal& rSignal, sdv::IInterfaceAccess* pEvent /*= nullptr*/) :
m_rSignal(rSignal)
{
if (pEvent) m_pEvent = pEvent->GetInterface<sdv::core::ISignalReceiveEvent>();
}
void CConsumer::DestroyObject()
{
m_rSignal.RemoveConsumer(this);
}
sdv::any_t CConsumer::Read(/*in*/ sdv::IInterfaceAccess* pTransaction) const
{
const CTransaction* pTransactionObj = static_cast<const CTransaction*>(pTransaction);
// Determine the transaction ID to use
uint64_t uiTransactionID = 0;
if (pTransactionObj) uiTransactionID = pTransactionObj->GetReadTransactionID();
// Request a read from the signal
return m_rSignal.ReadFromConsumer(uiTransactionID);
}
void CConsumer::Distribute(const sdv::any_t& ranyVal)
{
if (m_pEvent) m_pEvent->Receive(ranyVal);
}
CSignal::CSignal(CDispatchService& rDispatchSvc, const sdv::u8string& rssName, sdv::core::ESignalDirection eDirection,
sdv::any_t anyDefVal /*= sdv::any_t()*/) :
m_rDispatchSvc(rDispatchSvc), m_ssName(rssName), m_eDirection(eDirection), m_anyDefVal(anyDefVal)
{
for (auto& rprVal : m_rgprVal)
rprVal = {0, anyDefVal};
}
CSignal::~CSignal()
{}
sdv::u8string CSignal::GetName() const
{
return m_ssName;
}
sdv::core::ESignalDirection CSignal::GetDirection() const
{
return m_eDirection;
}
sdv::any_t CSignal::GetDefVal() const
{
return m_anyDefVal;
}
CProvider* CSignal::CreateProvider()
{
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
auto ptrProvider = std::make_unique<CProvider>(*this);
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an exception was
// triggered).
// cppcheck-suppress knownConditionTrueFalse
if (!ptrProvider)
return nullptr;
CProvider* pObject = ptrProvider.get();
if (pObject)
m_mapProviders.try_emplace(pObject, std::move(ptrProvider));
else
RemoveProvider(nullptr); // Cleanup
return pObject;
}
void CSignal::RemoveProvider(CProvider* pProvider)
{
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
m_mapProviders.erase(pProvider);
bool bUnregister = m_mapProviders.empty() && m_mapConsumers.empty();
lock.unlock();
if (bUnregister)
m_rDispatchSvc.UnregisterSignal(m_ssName, m_eDirection);
}
CConsumer* CSignal::CreateConsumer(sdv::IInterfaceAccess* pEvent /*= nullptr*/)
{
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
auto ptrConsumer = std::make_unique<CConsumer>(*this, pEvent);
// Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an exception was
// triggered).
// cppcheck-suppress knownConditionTrueFalse
if (!ptrConsumer)
return nullptr;
CConsumer* pObject = ptrConsumer.get();
if (pObject)
m_mapConsumers.try_emplace(pObject, std::move(ptrConsumer));
else
RemoveConsumer(nullptr); // Cleanup
return pObject;
}
void CSignal::RemoveConsumer(CConsumer* pConsumer)
{
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
m_mapConsumers.erase(pConsumer);
if (m_mapConsumers.empty())
{
// Remove any triggers
std::unique_lock<std::mutex> lockTrigger(m_mtxTriggers);
while (!m_setTriggers.empty())
{
CTrigger* pTrigger = *m_setTriggers.begin();
m_setTriggers.erase(m_setTriggers.begin());
lockTrigger.unlock();
// Remove this signal from the trigger.
pTrigger->RemoveSignal(m_ssName);
lockTrigger.lock();
}
}
bool bUnregister = m_mapProviders.empty() && m_mapConsumers.empty();
lock.unlock();
if (bUnregister)
m_rDispatchSvc.UnregisterSignal(m_ssName, m_eDirection);
}
void CSignal::WriteFromProvider(const sdv::any_t& ranyVal, uint64_t uiTransactionID, std::set<CTrigger*>& rsetTriggers)
{
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
uint64_t uiTransactionIDTemp = uiTransactionID;
if (!uiTransactionIDTemp) uiTransactionIDTemp = m_rDispatchSvc.GetDirectTransactionID();
// Store the value
std::unique_lock<std::mutex> lock(m_mtxVal);
if (m_nValIndex >= std::extent_v<decltype(m_rgprVal)>) m_nValIndex = 0; // TODO: This is a serious error.
size_t nTargetIndex = m_nValIndex;
// Create an entry with the current transaction ID
if (m_rgprVal[nTargetIndex].first < uiTransactionIDTemp)
{
nTargetIndex = (nTargetIndex + 1) % std::extent_v<decltype(m_rgprVal)>;
m_rgprVal[nTargetIndex].first = uiTransactionIDTemp;
m_nValIndex = nTargetIndex;
}
// Update the value
m_rgprVal[nTargetIndex].second = ranyVal;
lock.unlock();
// Add all triggers to the set of triggers
std::unique_lock<std::mutex> lockTriggers(m_mtxTriggers);
for (CTrigger* pTrigger : m_setTriggers)
rsetTriggers.insert(pTrigger);
lockTriggers.unlock();
// Trigger the update event.
DistributeToConsumers(ranyVal);
}
sdv::any_t CSignal::ReadFromConsumer(uint64_t uiTransactionID) const
{
std::unique_lock<std::mutex> lock(m_mtxVal);
if (m_nValIndex >= std::extent_v<decltype(m_rgprVal)>) return {}; // TODO: This is a serious error.
// Determine the transaction ID to use
uint64_t uiTransactionIDCopy = uiTransactionID;
size_t nTargetIndex = m_nValIndex;
if (!uiTransactionIDCopy) uiTransactionIDCopy = m_rgprVal[nTargetIndex].first;
// Find the signal with the same or lower transaction ID
while (m_rgprVal[m_nValIndex].first > uiTransactionIDCopy) // Value too new
{
// Search for the index with the transaction ID.
// Ignore cppcheck comparison operator (template is interpreted as > operator)
// cppcheck-suppress compareBoolExpressionWithInt
// cppcheck-suppress knownConditionTrueFalse
nTargetIndex = (nTargetIndex + std::extent_v<decltype(m_rgprVal)> - 1) % std::extent_v<decltype(m_rgprVal)>;
if (nTargetIndex == m_nValIndex) return m_anyDefVal; // Transaction too old... cannot update
}
// Fitting transaction found. Get the value.
sdv::any_t anyVal = m_rgprVal[nTargetIndex].second;
return anyVal;
}
void CSignal::DistributeToConsumers(const sdv::any_t& ranyVal)
{
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
std::unique_lock<std::mutex> lock(m_mtxSignalObjects);
for (auto& rvtConsumer : m_mapConsumers)
rvtConsumer.second->Distribute(ranyVal);
}
void CSignal::AddTrigger(CTrigger* pTrigger)
{
std::unique_lock<std::mutex> lock(m_mtxTriggers);
m_setTriggers.emplace(pTrigger);
}
void CSignal::RemoveTrigger(CTrigger* pTrigger)
{
std::unique_lock<std::mutex> lock(m_mtxTriggers);
m_setTriggers.erase(pTrigger);
}
bool CSignal::EqualsDefaultValue() const
{
return ReadFromConsumer(0) == m_anyDefVal;
}

View File

@@ -0,0 +1,238 @@
#ifndef SIGNAL_H
#define SIGNAL_H
#include <support/interface_ptr.h>
#include <interfaces/dispatch.h>
#include "trigger.h"
// Forward declaration
class CDispatchService;
class CSignal;
/**
* @brief Class implementing the signal provider. Needed for provider interface implementation.
*/
class CProvider : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::core::ISignalWrite
{
public:
/**
* @brief Constructor
* @param[in] rSignal Reference to the signal instance.
*/
explicit CProvider(CSignal& rSignal);
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
SDV_INTERFACE_ENTRY(sdv::core::ISignalWrite)
END_SDV_INTERFACE_MAP()
/**
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
*/
virtual void DestroyObject() override;
/**
* @brief Update the signal value. Overload of sdv::core::ISignalWrite::Write.
* @param[in] anyVal The value to update the signal with.
* @param[in] pTransaction The transaction interface. Could be NULL in case the update should occur immediately.
*/
virtual void Write(/*in*/ sdv::any_t anyVal, /*in*/ sdv::IInterfaceAccess* pTransaction) override;
/**
* @brief Update the signal value with the transaction ID supplied. A new entry will be created if the transaction is
* larger.
* @param[in] ranyVal Reference to the value to update the signal with.
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
*/
void Write(const sdv::any_t& ranyVal, uint64_t uiTransactionID);
private:
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
CSignal& m_rSignal; ///< Reference to the signal class.
};
/**
* @brief Class implementing the signal consumer. Needed for consumer interface implementation.
*/
class CConsumer : public sdv::IInterfaceAccess, public sdv::IObjectDestroy, public sdv::core::ISignalRead
{
public:
/**
* @brief Constructor
* @param[in] rSignal Reference to the signal instance.
* @param[in] pEvent The event to be triggered on a signal change. Optional.
*/
CConsumer(CSignal& rSignal, sdv::IInterfaceAccess* pEvent = nullptr);
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
SDV_INTERFACE_ENTRY(sdv::core::ISignalRead)
END_SDV_INTERFACE_MAP()
/**
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
*/
virtual void DestroyObject() override;
/**
* @brief Get the signal value. Overload of sdv::core::ISignalRead::Read.
* @param[in] pTransaction The transaction interface. Could be NULL in case the most up-to-date value is requested.
* @return Returns the value.
*/
virtual sdv::any_t Read(/*in*/ sdv::IInterfaceAccess* pTransaction) const override;
/**
* @brief Update the signal value with the transaction ID supplied. A new entry will be created if the transaction is
* larger.
* @param[in] ranyVal Reference to the value to update the signal with.
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
*/
void Write(const sdv::any_t& ranyVal, uint64_t uiTransactionID);
/**
* @brief Distribute a value to all consumers.
* @param[in] ranyVal Reference of the value to consumers.
*/
void Distribute(const sdv::any_t& ranyVal);
private:
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
CSignal& m_rSignal; ///< Reference to the signal class
sdv::core::ISignalReceiveEvent* m_pEvent = nullptr; ///< Receive event interface if available.
};
/**
* @brief Signal administration
*/
class CSignal
{
public:
/**
* @brief Helper object to manage object lifetime.
*/
struct SSignalObjectHelper
{
/**
* @brief Default destructor (needs to be virtual to allow deletion of derived class).
*/
virtual ~SSignalObjectHelper() = default;
};
/**
* @brief Signal class constructor.
* @param[in] rDispatchSvc Reference to the dispatch service.
* @param[in] rssName Reference to the name string.
* @param[in] anyDefVal Any containing the default value (for send-signals).
* @param[in] eDirection Definition whether the signal is a send or receive signal.
*/
CSignal(CDispatchService& rDispatchSvc, const sdv::u8string& rssName, sdv::core::ESignalDirection eDirection,
sdv::any_t anyDefVal = sdv::any_t());
/**
* @brief Destructor
*/
~CSignal();
/**
* @brief Get the name of the signal.
* @return String with the signal name.
*/
sdv::u8string GetName() const;
/**
* @brief Get the signal direction.
* @return The signal direction.
*/
sdv::core::ESignalDirection GetDirection() const;
/**
* @brief Get the signal default value.
* @return Any structure with default value.
*/
sdv::any_t GetDefVal() const;
/**
* @brief Create a provider object.
* @return Returns a pointer to the provider object or NULL when the signal is not configured to be a provider.
*/
CProvider* CreateProvider();
/**
* @brief Remove a provider object.
* @remarks If there are no other consumer/provider objects any more, the signal is unregistered from the dispatch service.
* @param[in] pProvider Pointer to the provider to remove.
*/
void RemoveProvider(CProvider* pProvider);
/**
* @brief Create a consumer object.
* @param[in] pEvent The event to be triggered on a signal change. Optional.
* @return Returns a pointer to the consumer object or NULL when the signal is not configured to be a consumer.
*/
CConsumer* CreateConsumer(sdv::IInterfaceAccess* pEvent = nullptr);
/**
* @brief Remove a consumer object.
* @remarks If there are no other consumer/provider objects any more, the signal is unregistered from the dispatch service.
* @param[in] pConsumer Pointer to the consumer to remove.
*/
void RemoveConsumer(CConsumer* pConsumer);
/**
* @brief Update the signal value with the transaction ID supplied. A new entry will be created if the transaction is larger.
* @param[in] ranyVal Reference to the value to update the signal with.
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
* @param[in] rsetTriggers Set of triggers to execute on a spontaneous write.
*/
void WriteFromProvider(const sdv::any_t& ranyVal, uint64_t uiTransactionID, std::set<CTrigger*>& rsetTriggers);
/**
* @brief Get the signal value.
* @param[in] uiTransactionID The transaction ID or 0 for current transaction ID.
* @return Returns the value.
*/
sdv::any_t ReadFromConsumer(uint64_t uiTransactionID) const;
/**
* @brief Distribute a value to all consumers.
* @param[in] ranyVal Reference of the value to distribute.
*/
void DistributeToConsumers(const sdv::any_t& ranyVal);
/**
* @brief Add a trigger to the signal. This trigger will be returned when creating a trigger list.
* @param[in] pTrigger Pointer to the trigger object.
*/
void AddTrigger(CTrigger* pTrigger);
/**
* @brief Remove the trigger from the signal.
* @param[in] pTrigger Pointer to the trigger object.
*/
void RemoveTrigger(CTrigger* pTrigger);
/**
* @brief Returns whether the signal equals the default value.
* @return Return the result of the comparison.
*/
bool EqualsDefaultValue() const;
private:
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
CDispatchService& m_rDispatchSvc; ///< Reference to dispatch service.
sdv::u8string m_ssName; ///< Signal name
sdv::core::ESignalDirection m_eDirection = sdv::core::ESignalDirection::sigdir_tx; ///< Signal direction
sdv::any_t m_anyDefVal; ///< Default value
mutable std::mutex m_mtxVal; ///< Signal value protection
mutable std::pair<uint64_t, sdv::any_t> m_rgprVal[16]; ///< The signal value
mutable size_t m_nValIndex = 0; ///< Most up-to-date-index
mutable std::mutex m_mtxSignalObjects; ///< Signal object map protection.
std::map<CProvider*, std::unique_ptr<CProvider>> m_mapProviders; ///< Map with signal objects.
std::map<CConsumer*, std::unique_ptr<CConsumer>> m_mapConsumers; ///< Map with signal objects.
std::mutex m_mtxTriggers; ///< Protection for the trigger set.
std::set<CTrigger*> m_setTriggers; ///< Trigger set for this signal.
};
#endif // !defined SIGNAL_H

View File

@@ -0,0 +1,90 @@
#include "dispatchservice.h"
#include "transaction.h"
#include "trigger.h"
#include "signal.h"
CTransaction::CTransaction(CDispatchService& rDispatchSvc) :
m_rDispatchSvc(rDispatchSvc), m_uiReadTransactionID(rDispatchSvc.GetNextTransactionID())
{}
void CTransaction::DestroyObject()
{
FinalizeWrite();
m_rDispatchSvc.FinishTransaction(this);
}
uint64_t CTransaction::GetReadTransactionID() const
{
// Set transaction to write if not done so.
if (m_eTransactionType != ETransactionType::read_transaction)
{
if (m_eTransactionType == ETransactionType::undefined)
{
m_eTransactionType = ETransactionType::read_transaction;
// Create a new direct transaction ID to prevent the current values from overwriting
m_rDispatchSvc.CreateDirectTransactionID();
}
else
return 0ull;
}
return m_uiReadTransactionID;
}
void CTransaction::DeferWrite(CSignal& rSignal, sdv::any_t& ranyVal)
{
// Set transaction to write if not done so.
if (m_eTransactionType != ETransactionType::write_transaction)
{
if (m_eTransactionType == ETransactionType::undefined)
m_eTransactionType = ETransactionType::write_transaction;
else
return;
}
// Add the value to the deferred signal map.
std::unique_lock<std::mutex> lock(m_mtxDeferredWriteSignalMap);
m_mapDeferredWriteSignalMap.insert_or_assign(&rSignal, ranyVal);
}
void CTransaction::FinalizeWrite()
{
// Set transaction to write if not done so.
if (m_eTransactionType != ETransactionType::write_transaction)
{
if (m_eTransactionType == ETransactionType::undefined)
m_eTransactionType = ETransactionType::write_transaction;
else
return;
}
// Get the next transaction ID
uint64_t uiWriteTransaction = m_rDispatchSvc.GetNextTransactionID();
// Write the signals with the transaction ID.
std::unique_lock<std::mutex> lock(m_mtxDeferredWriteSignalMap);
std::set<CTrigger*> setTriggers;
for (auto& rvtDeferredSignal : m_mapDeferredWriteSignalMap)
{
if (!rvtDeferredSignal.first) continue;
rvtDeferredSignal.first->WriteFromProvider(rvtDeferredSignal.second, uiWriteTransaction, setTriggers);
}
m_mapDeferredWriteSignalMap.clear();
lock.unlock();
// Execute the triggers
for (CTrigger* pTrigger : setTriggers)
pTrigger->Execute();
}
void CTransaction::SetIterator(std::list<CTransaction>::iterator itTransaction)
{
m_itTransaction = itTransaction;
}
std::list<CTransaction>::iterator CTransaction::GetIterator() const
{
return m_itTransaction;
}

View File

@@ -0,0 +1,99 @@
#ifndef TRANSACTION_H
#define TRANSACTION_H
#include <support/interface_ptr.h>
// Forward declaration
class CDispatchService;
class CSignal;
/**
* @brief Transaction administration
* @details During creation of the transaction object, the transaction is of undefined type. The transaction takes the read or
* write type only after a call to the read or write function. After this, the transaction doesn't change type any more and will
* block calls to the functions provided for the other type (e.g. a read transaction doesn't allow a write and a write transaction
* doesn't allow a read).
* A read transaction is using the transaction ID to identify the current time of the transaction. Any data available before can
* be read. Any data coming after will not be transmitted. In case there is no data any more (the transaction is too old) the
* default value will be used.
* A write transaction will collect the signal values. The distribution is deferred until the transaction is finalized. During
* finalization, a new transaction ID is requested and the values are distributed using this ID (this is necessary so a read
* transaction can decide whether the values are included in a read operation).
* The written values are stored in a ringbuffer in the signal class. The latest position in the ring buffer contains the last
* distributed transaction.
*/
class CTransaction : public sdv::IInterfaceAccess, public sdv::IObjectDestroy
{
public:
/**
* @brief Constructor
* @param[in] rDispatchSvc Reference to the dispatch service.
*/
explicit CTransaction(CDispatchService& rDispatchSvc);
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess)
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
END_SDV_INTERFACE_MAP()
/**
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
*/
virtual void DestroyObject() override;
/**
* @brief When called, enables the transaction as read-transaction. This would only happen when the transaction is still in
* undefined state.
* @return Returns the read-transaction ID or 0 when the transaction is not registered as read-transaction.
*/
uint64_t GetReadTransactionID() const;
/**
* @brief When called, enables the transaction as write-transaction. This would only happen when the transaction is still in
* undefined state. In that case, the value will be added to the deferred signal map. Any previous value will be overwritten.
* @param[in] rSignal Reference to the signal class.
* @param[in] ranyVal Reference to the value.
*/
void DeferWrite(CSignal& rSignal, sdv::any_t& ranyVal);
/**
* @brief Finalizes the transaction. For read transactions, nothing happens. For write transactions, a transaction ID is stored
* with the written signal value.
*/
void FinalizeWrite();
/**
* @brief Set the iterator to this transaction (prevents having to search for the transaction in the transaction list).
* @param[in] itTransaction The iterator to store.
*/
void SetIterator(std::list<CTransaction>::iterator itTransaction);
/**
* @brief Get the stored iterator to the transaction.
* @return The stored iterator.
*/
std::list<CTransaction>::iterator GetIterator() const;
private:
/**
* @brief Transaction type
*/
enum class ETransactionType
{
undefined = 0, ///< Transaction type was not defined yet.
read_transaction = 1, ///< Read transaction (for TX signals).
write_transaction = 2, ///< Write transaction (for RX signals and publishers).
};
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
CDispatchService& m_rDispatchSvc; ///< Reference to dispatch service.
mutable ETransactionType m_eTransactionType = ETransactionType::undefined; ///< Transaction type.
std::mutex m_mtxDeferredWriteSignalMap; ///< Deferred write signal map access.
std::map<CSignal*, sdv::any_t> m_mapDeferredWriteSignalMap; ///< Deferred write signal map.
uint64_t m_uiReadTransactionID = 0ull; ///< Read transaction ID.
std::list<CTransaction>::iterator m_itTransaction{}; ///< Iterator of the transaction in the transaction list.
};
#endif // !defined TRANSACTION_H

View File

@@ -0,0 +1,205 @@
#include "dispatchservice.h"
#include "trigger.h"
#include "signal.h"
CScheduler::~CScheduler()
{
Stop();
}
void CScheduler::Start()
{
// Start the timer at 1ms rate
m_timer = sdv::core::CTaskTimer(1, [&]() { EvaluateAndExecute(); });
}
void CScheduler::Stop()
{
// Stop the timer
m_timer.Reset();
// Clear the schedule lists
std::unique_lock<std::mutex> lock(m_mtxScheduler);
m_mapTriggers.clear();
m_mmapScheduleList.clear();
}
void CScheduler::Schedule(CTrigger* pTrigger, EExecutionFlag eExecFlag, std::chrono::high_resolution_clock::time_point tpDue)
{
if (!pTrigger) return;
if (!m_timer) return;
// Check whether a job is scheduled already for this trigger object
std::unique_lock<std::mutex> lock(m_mtxScheduler);
auto itObject = m_mapTriggers.find(pTrigger);
if (itObject != m_mapTriggers.end())
{
// Trigger execution of the object already found. Update the periodic flag if not set necessary
if (eExecFlag == EExecutionFlag::spontaneous)
itObject->second = eExecFlag;
return;
}
// Insert the object into the scheduled object map.
itObject = m_mapTriggers.emplace(pTrigger, eExecFlag).first;
if (itObject == m_mapTriggers.end()) return; // Should not happen
// Schedule the execution
m_mmapScheduleList.emplace(tpDue, itObject);
}
void CScheduler::EvaluateAndExecute()
{
// Run until there is nothing to execute for the moment
while (true)
{
// Check the schedule list
std::unique_lock<std::mutex> lock(m_mtxScheduler);
auto itJob = m_mmapScheduleList.begin();
if (itJob == m_mmapScheduleList.end()) return; // Nothing to do
// Get the current time and check whether to execute
std::chrono::high_resolution_clock::time_point tpNow = std::chrono::high_resolution_clock::now();
if (tpNow < itJob->first) return; // No time yet
// This job is being scheduled; copy the information and remove the entries from the scheduler
CTrigger* pTrigger = itJob->second->first;
EExecutionFlag eExecFlag = itJob->second->second;
m_mapTriggers.erase(itJob->second);
m_mmapScheduleList.erase(itJob);
lock.unlock();
// Execute the trigger
pTrigger->Execute(eExecFlag);
}
}
void CScheduler::RemoveFromSchedule(const CTrigger* pTrigger)
{
// Search for the job in the scheduled list
std::unique_lock<std::mutex> lock(m_mtxScheduler);
auto itJob = std::find_if(m_mmapScheduleList.begin(), m_mmapScheduleList.end(),
[&](const auto& rvtJob) { return rvtJob.second->first == pTrigger; });
if (itJob == m_mmapScheduleList.end()) return; // No scheduled jobs found
// Remove the job
m_mapTriggers.erase(itJob->second);
m_mmapScheduleList.erase(itJob);
}
CTrigger::CTrigger(CDispatchService& rDispatchSvc, uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
sdv::core::ITxTriggerCallback* pCallback) :
m_rDispatchSvc(rDispatchSvc), m_tpLast{}, m_nPeriod(uiCycleTime), m_nDelay(uiDelayTime), m_uiBehaviorFlags(uiBehaviorFlags),
m_pCallback(pCallback)
{
// Start the timer if this triggr should be perodic
if (uiCycleTime)
m_timer = sdv::core::CTaskTimer(uiCycleTime, [&]() { Execute(EExecutionFlag::periodic); });
}
bool CTrigger::IsValid() const
{
if (!m_pCallback) return false;
if (m_nPeriod && !m_timer) return false;
if (!m_nPeriod && !(m_uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
return false;
return true;
}
void CTrigger::DestroyObject()
{
// Stop the timer
m_timer.Reset();
// Remove all signals
std::unique_lock<std::mutex> lock(m_mtxSignals);
while (!m_mapSignals.empty())
{
CSignal* pSignal = m_mapSignals.begin()->second;
m_mapSignals.erase(m_mapSignals.begin());
if (!pSignal) continue;
lock.unlock();
pSignal->RemoveTrigger(this);
lock.lock();
}
lock.unlock();
// Remove the object (this will destruct this object).
m_rDispatchSvc.RemoveTxTrigger(this);
}
bool CTrigger::AddSignal(/*in*/ const sdv::u8string& ssSignalName)
{
CSignal* pSignal = m_rDispatchSvc.FindSignal(ssSignalName, sdv::core::ESignalDirection::sigdir_tx);
if (pSignal && pSignal->GetDirection() == sdv::core::ESignalDirection::sigdir_tx)
{
std::unique_lock<std::mutex> lock(m_mtxSignals);
pSignal->AddTrigger(this);
m_mapSignals.insert(std::make_pair(pSignal->GetName(), pSignal));
return true;
}
return false;
}
void CTrigger::RemoveSignal(/*in*/ const sdv::u8string& ssSignalName)
{
std::unique_lock<std::mutex> lock(m_mtxSignals);
auto itSignal = m_mapSignals.find(ssSignalName);
if (itSignal == m_mapSignals.end()) return;
CSignal* pSignal = itSignal->second;
m_mapSignals.erase(itSignal);
lock.unlock();
if (pSignal) pSignal->RemoveTrigger(this);
}
void CTrigger::Execute(EExecutionFlag eExecFlag /*= EExecutionFlag::spontaneous*/)
{
if (m_rDispatchSvc.GetStatus() != sdv::EObjectStatus::running) return;
// Check for allowed execution
if (eExecFlag == EExecutionFlag::spontaneous &&
!(m_uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::spontaneous)))
return;
// Is there a delay time, check for the delay
std::chrono::high_resolution_clock::time_point tpNow = std::chrono::high_resolution_clock::now();
if (m_nDelay)
{
// Calculate earliest execution time
std::chrono::high_resolution_clock::time_point tpAllowedExecution = m_tpLast + std::chrono::milliseconds(m_nDelay);
// Is execution allowed already; if not, schedule execution for later.
if (tpNow < tpAllowedExecution)
{
m_rDispatchSvc.GetScheduler().Schedule(this, eExecFlag, tpAllowedExecution);
return;
}
}
// Check for the active flag. If periodic send when active is enabled, check the data for its state and update the repition
// counter.
if (m_uiBehaviorFlags & static_cast<uint32_t>(sdv::core::ISignalTransmission::ETxTriggerBehavior::periodic_if_active))
{
// Check whether content is active
std::unique_lock<std::mutex> lock(m_mtxSignals);
bool bDefault = true;
for (const auto& rvtSignal : m_mapSignals)
bDefault &= rvtSignal.second->EqualsDefaultValue();
// Reset or increase repetition counter based on activity
m_nInactiveRepetition = bDefault ? m_nInactiveRepetition + 1 : 0;
// Based on the inactive repitions, decide to execute.
// FIXED VALUE: Inactive repitions is set to 1... could be defined dynamic in the future
if (eExecFlag == EExecutionFlag::periodic && m_nInactiveRepetition > 1) return;
}
// Execution is allowed, update execution timepoint
m_tpLast = tpNow;
// Execute the trigger
if (m_pCallback) m_pCallback->Execute();
}

View File

@@ -0,0 +1,155 @@
#ifndef TRIGGER_H
#define TRIGGER_H
#include <support/interface_ptr.h>
#include <support/timer.h>
// Forward declaration
class CDispatchService;
class CSignal;
class CTrigger;
/**
* @brief Flag indicating whether the execution is part of a periodic update or is spontaneous.
*/
enum class EExecutionFlag
{
periodic, ///< Execution is triggered periodically
spontaneous ///< Spontaneous execution triggered
};
/**
* @brief Scheduler class being used to schedule a deferred trigger execution when the minimal time between execution is undercut.
*/
class CScheduler
{
public:
/**
* @brief Default constructor
*/
CScheduler() = default;
/**
* @brief Destructor
*/
~CScheduler();
/**
* @brief Start the scheduler. This will start the scheduler timer at a 1ms rate.
*/
void Start();
/**
* @brief Stop the scheduler. This will stop the scheduler timer and clear all pending schedule jobs.
*/
void Stop();
/**
* @brief Schedule a new trigger execution for the trigger object.
* @details If a scheduled trigger execution already exists for the object, the trigger execution is not scheduled again. If
* the already scheduled trigger execution was marked as periodic and the new trigger is a spontenous trigger, the scheduled
* execution is updated to represent a spontaneous execution (periodic triggers are sometimes not fired when the data hasn't
* changed).
* @param[in] pTrigger Pointer to the trigger object.
* @param[in] eExecFlag Set the trigger execution flag.
* @param[in] tpDue The due time this trigger is to be executed.
*/
void Schedule(CTrigger* pTrigger, EExecutionFlag eExecFlag, std::chrono::high_resolution_clock::time_point tpDue);
/**
* @brief In case the trigger object will be destroyed, remove all pending schedule jobs from the scheduler.
* @param[in] pTrigger Pointer to the trigger object.
*/
void RemoveFromSchedule(const CTrigger* pTrigger);
private:
/**
* @brief Evaluate a potential job execution.
*/
void EvaluateAndExecute();
/// Map containing the scheduled objects and the execution flag for the object.
using CObjectMap = std::map<CTrigger*, EExecutionFlag>;
/// Multi-map containing the scheduled target time and an iterator to the entry of the scheduler object map.
using CSchedulerMMap = std::multimap<std::chrono::high_resolution_clock::time_point, CObjectMap::iterator>;
sdv::core::CTaskTimer m_timer; ///< 1ms timer
std::mutex m_mtxScheduler; ///< Protection of the schedule list
CObjectMap m_mapTriggers; ///< Map with the currently scheduled trigger objects (prevents new
///< triggers to be scheduled for the same trigger object).
CSchedulerMMap m_mmapScheduleList; ///< Schedule lst.
};
/**
* @brief Trigger object, managing the triggering.
*/
class CTrigger : public sdv::IInterfaceAccess, public sdv::core::ITxTrigger, public sdv::IObjectDestroy
{
public:
/**
* @brief Constructor
* @param[in] rDispatchSvc Reference to the dispatch service.
* @param[in] uiCycleTime When set to any value other than 0, provides a cyclic trigger (ms). Could be 0 if cyclic
* triggering is not required.
* @param[in] uiDelayTime When set to any value other than 0, ensures a minimum time between two triggers. Could be 0
* if minimum time should not be enforced.
* @param[in] uiBehaviorFlags Zero or more flags from sdv::core::ETxTriggerBehavior.
* @param[in] pCallback Pointer to the callback interface.
*/
CTrigger(CDispatchService& rDispatchSvc, uint32_t uiCycleTime, uint32_t uiDelayTime, uint32_t uiBehaviorFlags,
sdv::core::ITxTriggerCallback* pCallback);
// Interface map
BEGIN_SDV_INTERFACE_MAP()
SDV_INTERFACE_ENTRY(sdv::IInterfaceAccess)
SDV_INTERFACE_ENTRY(sdv::core::ITxTrigger)
SDV_INTERFACE_ENTRY(sdv::IObjectDestroy)
END_SDV_INTERFACE_MAP()
/**
* @brief Return whether the trigger object is valid.
* @return Returns the validity of the trigger.
*/
bool IsValid() const;
/**
* @brief Destroy the object. Overload of sdv::IObjectDestroy::DestroyObject.
*/
virtual void DestroyObject() override;
/**
* @brief Add a signal to the trigger object. The signal must be registered as TX signal before.
* Overload of sdv::core::ITxTrigger::AddSignal.
* @param[in] ssSignalName Name of the signal.
* @return Returns whether adding the signal was successful.
*/
virtual bool AddSignal(/*in*/ const sdv::u8string& ssSignalName) override;
/**
* @brief Remove a signal from the trigger object.Overload of sdv::core::ITxTrigger::RemoveSignal.
* @param[in] ssSignalName Name of the signal.
*/
virtual void RemoveSignal(/*in*/ const sdv::u8string& ssSignalName) override;
/**
* @brief This function is triggered every ms.
* @param[in] eExecFlag When set, the trigger was caused by the periodic timer.
*/
void Execute(EExecutionFlag eExecFlag = EExecutionFlag::spontaneous);
private:
sdv::CLifetimeCookie m_cookie = sdv::CreateLifetimeCookie(); ///< Lifetime cookie to manage the module lifetime.
CDispatchService& m_rDispatchSvc; ///< Reference to dispatch service.
sdv::core::CTaskTimer m_timer; ///< Task timer event object.
std::chrono::high_resolution_clock::time_point m_tpLast; ///< Timepoint of the last trigger
size_t m_nPeriod = 0ull; ///< The period to trigger.
size_t m_nDelay = 0ull; ///< The minimum delay between two triggers.
uint32_t m_uiBehaviorFlags = 0ul; ///< Additional behavior
size_t m_nInactiveRepetition = 0ull; ///< Count the amount of inactive executions.
sdv::core::ITxTriggerCallback* m_pCallback = nullptr; ///< Callback pointer
std::mutex m_mtxSignals; ///< Signal map protection.
std::map<sdv::u8string, CSignal*> m_mapSignals; ///< Assigned signals.
};
#endif // ! defined TRIGGER_H