Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
497 changes: 93 additions & 404 deletions score/time_daemon/docs/detailed_design/index.rst

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions score/time_daemon/src/application/time_daemon.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ namespace score
namespace td
{

/// @brief Main entry point for the TimeDaemon process. Orchestrates the lifecycle
/// of all daemon components.
///
/// Retrieves Vehicle Time from the PTP slave daemon, verifies and validates
/// timepoints, and distributes time to clients via shared memory. Uses
/// MachineFactory to create components and wire pub/sub relationships via
/// MessageBroker.
///
/// @see TimebaseHandler
/// @see score::mw::lifecycle::Application
class TimeDaemon final : public score::mw::lifecycle::Application
{
public:
Expand All @@ -33,7 +43,26 @@ class TimeDaemon final : public score::mw::lifecycle::Application
TimeDaemon& operator=(TimeDaemon&&) & noexcept = delete;
TimeDaemon& operator=(const TimeDaemon&) & noexcept = delete;

/// @brief Creates the MessageBroker and all machine components, and sets up subscriptions.
///
/// Creates components in dependency order:
/// 1. MessageBroker
/// 2. ProactiveMachines: PtpMachine, ControlFlowDivider
/// 3. ReactiveMachines: VerificationMachine, IPCMachine
///
/// Wires all pub/sub relationships via MessageBroker topic configuration.
///
/// @param context Application context from the middleware.
/// @return 0 on success, non-zero on failure.
std::int32_t Initialize(const score::mw::lifecycle::ApplicationContext& context) override;

/// @brief Starts the ProactiveMachines and monitors the stop token.
///
/// Execution loop monitors stop_token. On termination request, stops all
/// ProactiveMachines in reverse order of startup for clean shutdown.
///
/// @param token Stop token for graceful shutdown.
/// @return 0 on success, non-zero on failure.
std::int32_t Run(const score::cpp::stop_token& token) override;

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,28 @@ namespace score
namespace td
{

/**
* @brief Component responsible for decoupling data flow and timing within VehicleTimeDaemon.
*
* The ControlFlowDivider acts as a buffer between data producers and consumers, ensuring
* that timing-sensitive operations are not affected by blocking operations in downstream
* components. It maintains consistent data publishing rates by using a dedicated thread
* and internal buffering. When new data arrives, it processes all available data immediately.
* If no new data arrives within the specified timeout, it republishes the last known data
* to maintain consistent output timing.
*
* @tparam DataType The type of data being processed (e.g., PtpTimeInfo)
* @tparam BufferSize The size of the internal circular buffer for storing incoming data
*/
/// @brief Component responsible for decoupling data flow and timing within VehicleTimeDaemon.
///
/// The ControlFlowDivider acts as a buffer between data producers and consumers, ensuring
/// that timing-sensitive operations are not affected by blocking operations in downstream
/// components. It maintains consistent data publishing rates by using a dedicated thread
/// and internal buffering. When new data arrives, it processes all available data immediately.
/// If no new data arrives within the specified timeout, it republishes the last known data
/// to maintain consistent output timing.
///
/// Thread safety: @c OnMessage() called from MessageBroker thread; @c OnEvent() / @c OnTimeout()
/// called from worker thread. Synchronized via @c data_buffer_mutex_.
///
/// @tparam DataType The type of data being processed (e.g. PtpTimeInfo).
/// @tparam BufferSize The size of the internal circular buffer for incoming data.
template <typename DataType, size_t BufferSize>
class ControlFlowDivider final : public EventDrivenMachine, public Consumer<DataType>, public Producer<DataType>
{
public:
/**
* @brief Constructs a ControlFlowDivider with specified timeout.
*
* @param name The name of this control flow divider instance
* @param timeout Maximum time to wait for new data before republishing the last known data
*/
/// @brief Constructs a ControlFlowDivider with the specified timeout.
///
/// @param name The name of this control flow divider instance.
/// @param timeout Maximum time to wait for new data before republishing the last known data.
explicit ControlFlowDivider(const std::string& name, std::chrono::milliseconds timeout);

~ControlFlowDivider() override = default;
Expand All @@ -64,50 +63,48 @@ class ControlFlowDivider final : public EventDrivenMachine, public Consumer<Data
ControlFlowDivider(ControlFlowDivider&&) = delete;
ControlFlowDivider& operator=(ControlFlowDivider&&) = delete;

/**
* @brief Initialize machine
*
* As there is no explicit Init actions, it will be stubbed and return true.
*
* @param bool Init result
*/
/// @brief Initialise the machine. Stubbed — returns true immediately as no
/// explicit initialisation actions are required.
bool Init() override;

/**
* @brief Sets the callback function to be invoked when publishing data.
*
* @param callback Function to be called when data is published
*/
/// @brief Sets the callback function to be invoked when publishing data.
///
/// @param callback Function to be called when data is published.
void SetPublishCallback(std::function<void(const DataType&)> callback) override;

/**
* @brief Receives new data and queues it for processing.
*
* This method receives incoming data, stores it in the internal buffer,
* and triggers the event-driven processing mechanism to handle the data.
* The operation is thread-safe and non-blocking.
*
* @param data The data to be queued for processing
*/
/// @brief Receives new data and queues it for asynchronous processing.
///
/// This method receives incoming data, stores it in the internal buffer,
/// and triggers the event-driven processing mechanism to handle the data.
/// The operation is thread-safe and non-blocking.
///
/// @param data The data to be queued for processing.
void OnMessage(DataType data) override;

protected:
/// @brief Drains the circular buffer and publishes each queued item in FIFO order.
///
/// Called by the EventDrivenMachine worker thread when @c OnMessage() triggers
/// an event. Acquires @c data_buffer_mutex_ and publishes all pending items.
void OnEvent() noexcept override;

/// @brief Republishes the last known data when no new data has arrived within the timeout.
///
/// Called by the EventDrivenMachine worker thread on timeout expiry. Publishes
/// the cached @c last_data_ to preserve a consistent output cadence even when
/// the upstream data flow stalls.
void OnTimeout() noexcept override;

private:
/**
* @brief Publishes data to the Message Broker.
*
* @param data The data to be published
*/
/// @brief Publishes data to the MessageBroker via the registered callback.
///
/// @param data The data to be published.
void Publish(const DataType& data) override;

std::mutex data_buffer_mutex_;
score::cpp::circular_buffer<DataType, BufferSize> data_buffer_;

/** @brief Callback function for publishing data */
/// @brief Callback function for publishing data
std::function<void(const DataType&)> publish_callback_;

DataType last_data_;
Expand Down
36 changes: 35 additions & 1 deletion score/time_daemon/src/ipc/core/publisher_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,33 @@ namespace score
namespace td
{

/// @brief IPC Machine publisher component that distributes verified time data
/// to client applications via shared memory.
///
/// \brief Publisher class to publish data through ipc to Client
/// Receives verified @c PtpTimeInfo from the MessageBroker (via the
/// @c verified_ptp_data topic), converts it to the target IPC format (e.g.
/// @c svt::TimeBaseSnapshot), and writes it to a custom shared memory channel
/// for consumption by VehicleClock backend clients.
///
/// Runs in reactive mode: @c OnMessage() is called by MessageBroker when new
/// @c verified_ptp_data arrives. @c SharedMemoryHandler::Send() is thread-safe
/// for a single writer (only one thread may call @c OnMessage() concurrently);
/// multiple readers on the application side can access the segment concurrently.
///
/// @tparam DataType Internal data type (e.g. PtpTimeInfo from MessageBroker).
/// @tparam IpcDataType Shared memory IPC data type (e.g. svt::TimeBaseSnapshot).
///
/// @see SvtPublisher Type alias for PublisherImpl<PtpTimeInfo, svt::TimeBaseSnapshot>
/// @see SharedMemoryHandler Shared memory management implementation
/// @see ConvertToIpcData Data conversion function
template <typename DataType, typename IpcDataType>
class PublisherImpl : public ReactiveMachine, public Consumer<DataType>
{
public:
/// @brief Constructs the IPC publisher with a shared memory path.
///
/// @param name Publisher instance name (used for logging).
/// @param shared_memory_path POSIX shared memory name (e.g. /td_svt_ipc).
PublisherImpl(const std::string& name, const std::string& shared_memory_path) noexcept
: ReactiveMachine(name), Consumer<DataType>(), shm_handler_{shared_memory_path}
{
Expand All @@ -43,7 +63,21 @@ class PublisherImpl : public ReactiveMachine, public Consumer<DataType>
PublisherImpl& operator=(PublisherImpl&&) = delete;
~PublisherImpl() override = default;

/// @brief Initialises the shared memory IPC channel.
///
/// Creates the backing shared memory segment for downstream readers via
/// @c SharedMemoryHandler::Init(). Must be called once before @c OnMessage().
///
/// @return true if the shared memory segment was created successfully.
bool Init() override;

/// @brief Receives verified time data from the MessageBroker and publishes to shared memory.
///
/// Converts @c DataType to @c IpcDataType via @c ConvertToIpcData(), then calls
/// @c SharedMemoryHandler::Send() which performs a single-writer atomic write
/// into the shared memory segment.
///
/// @param data PtpTimeInfo from the @c verified_ptp_data topic.
void OnMessage(DataType data) override;

private:
Expand Down
28 changes: 27 additions & 1 deletion score/time_daemon/src/ipc/core/receiver_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,28 @@ namespace score
namespace td
{

/// @brief IPC Machine receiver component for reading verified time data from
/// the shared memory channel written by @c PublisherImpl.
///
/// \brief Receiver impl class to receive data from ipc
/// Opens an existing shared memory channel and reads the latest IPC-formatted
/// time data. Used by the VehicleClock backend on the application
/// side to retrieve time information from TimeDaemon.
///
/// Thread-safe for multiple concurrent readers.
/// @c SharedMemoryHandler::Receive() returns the latest snapshot atomically.
/// The call is non-blocking and returns @c std::nullopt if no data is available.
///
/// @tparam IpcDataType Shared memory IPC data type (e.g. @c svt::TimeBaseSnapshot).
///
/// @see SvtReceiver Type alias for ReceiverImpl<svt::TimeBaseSnapshot>
/// @see PublisherImpl TimeDaemon-side publisher component
template <typename IpcDataType>
class ReceiverImpl : public Receiver<IpcDataType>
{
public:
/// @brief Constructs the IPC receiver with a shared memory path.
///
/// @param shared_memory_path POSIX shared memory name matching the publisher (e.g. /td_svt_ipc).
ReceiverImpl(const std::string& shared_memory_path) noexcept
: Receiver<IpcDataType>(), shm_handler_{shared_memory_path}
{
Expand All @@ -40,8 +55,19 @@ class ReceiverImpl : public Receiver<IpcDataType>
ReceiverImpl& operator=(ReceiverImpl&&) = delete;
~ReceiverImpl() override = default;

/// @brief Opens the existing shared memory IPC channel created by the publisher.
///
/// Connects to the already-created segment by name; does not create a new segment.
/// Non-blocking; returns false immediately if segment not found.
///
/// @return true if the shared memory segment was opened successfully.
bool Init() noexcept override;

/// @brief Reads the latest time data from shared memory.
///
/// Non-blocking. Returns @c std::nullopt if no new or valid sample is available.
///
/// @return Latest IPC data if available, @c std::nullopt otherwise.
std::optional<IpcDataType> Receive() noexcept override;

private:
Expand Down
31 changes: 27 additions & 4 deletions score/time_daemon/src/msg_broker/msg_broker.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,40 @@ namespace score
namespace td
{

/// @brief Central publish-subscribe communication hub within the TimeDaemon.
///
/// \brief Class to store each subscriber for dedicated topic. Idea is simple, any component can use subscribe with
/// dedicated callback
/// for explicit "topic". Then callback will be invoked when some component will call OnNewData() for dedicated
/// topic.
/// Manages topics and distributes messages to interested subscribers, enabling
/// decoupled communication: components evolve independently without direct
/// dependencies on each other.
///
/// MessageBroker does not provide synchronization between publish and callback
/// invocation. Callbacks run synchronously in caller's thread; no queuing.
/// To separate control flows, use @c ControlFlowDivider.
///
/// @tparam T Message data type for this broker instance (e.g. PtpTimeInfo).
///
/// @see ControlFlowDivider For control flow separation between threads.
template <typename T>
class MessageBroker : public std::enable_shared_from_this<MessageBroker<T>>
{
public:
/// @brief Registers a consumer component to receive messages on the specified topic.
///
/// Creates a subscription wrapping the consumer's @c OnMessage() callback and
/// adds it to the topic registry. Uses a weak_ptr to avoid prolonging the
/// consumer's lifetime.
///
/// @param topic Topic name to subscribe to.
/// @param subscriber_weak Weak pointer to the consumer component.
void AddSubscriber(const Topic& topic, std::weak_ptr<Consumer<T>> subscriber_weak);

/// @brief Registers a producer component to publish messages on the specified topic.
///
/// Sets the producer's publish callback to invoke @c OnNewData() on this broker.
/// Uses a weak_ptr to avoid prolonging the producer's lifetime.
///
/// @param topic Topic name to publish on.
/// @param producer_weak Weak pointer to the producer component.
void AddProducer(const Topic& topic, std::weak_ptr<Producer<T>> producer_weak);

private:
Expand Down
Loading
Loading