Initial UI setup

This commit is contained in:
Sylvain Schneider
2026-05-22 17:24:30 +02:00
parent 975717cb67
commit 0165dfe8f0
316 changed files with 78404 additions and 31 deletions

View File

@@ -0,0 +1,289 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "ringBuffer.h"
namespace sdi_toolBox::common::utils
{
//--------------------------------------------------------------
/**
* @brief Fixed-size circular buffer with compile-time capacity
* and no-overwrite behavior.
*
* This template wraps a RingBuffer and prevents new elements from being
* written when the buffer is full. Existing data is never overwritten;
* push() simply fails and returns false when capacity is reached.
*
* @tparam T Element type stored in the buffer.
* @tparam CAPACITY Compile-time buffer capacity. Must be > 0.
*
* @note The class provides both optional-returning and out-parameter
* overloads for pop/front/back to suit different runtime constraints.
* @warning Instantiating with CAPACITY == 0 is forbidden (static_assert).
*
* @code{.cpp}
* CircularBuffer<int, 8> cb;
* if (cb.push(42))
* {
* // element was inserted
* }
* else
* {
* // buffer was full, element was discarded
* }
* @endcode
*/
template<class T, std::size_t CAPACITY>
class CircularBuffer
{
static_assert(CAPACITY > 0, "CAPACITY must be > 0");
public:
///@name Write operations
///@{
/**
* @brief Try to append a value to the buffer.
*
* If the buffer is full, the value is discarded and no overwrite occurs.
* @param value Value to append (copied).
*
* @return true if the value was inserted, false if the buffer was full.
*/
bool push(const T &value);
///@}
///@name Read operations
///@{
/**
* @brief Remove and return the oldest element from the buffer.
*
* @return std::optional<T> The oldest element if present, std::nullopt if empty.
*/
std::optional<T> pop();
/**
* @brief Remove the oldest element and store it in the provided reference.
*
* @param value Output reference that receives the removed element.
*
* @return true if an element was removed, false if the buffer was empty.
*/
bool pop(T &value);
/**
* @brief Return (without removing) the oldest element.
*
* @return std::optional<T> The oldest element if present, std::nullopt if empty.
*/
[[nodiscard]] std::optional<T> front() const;
/**
* @brief Copy the oldest element into the provided reference without removing it.
*
* @param value Output reference that receives the element.
*
* @return true if the element was copied, false if the buffer is empty.
*/
[[nodiscard]] bool front(T &value) const;
/**
* @brief Return (without removing) the newest element.
*
* @return std::optional<T> The newest element if present, std::nullopt if empty.
*/
[[nodiscard]] std::optional<T> back() const;
/**
* @brief Copy the newest element into the provided reference without removing it.
*
* @param value Output reference that receives the element.
*
* @return true if the element was copied, false if the buffer is empty.
*/
[[nodiscard]] bool back(T &value) const;
///@}
///@name State queries
///@{
/**
* @brief Check whether the buffer is empty.
*
* @return true if empty, false otherwise.
*/
[[nodiscard]] bool empty() const;
/**
* @brief Check whether the buffer is full.
*
* @return true if full, false otherwise.
*/
[[nodiscard]] bool full() const;
/**
* @brief Number of elements currently stored in the buffer.
*
* @return Current size (0 .. CAPACITY).
*/
[[nodiscard]] std::size_t size() const;
/**
* @brief Compile-time capacity of the buffer.
*
* @return The maximum number of elements the buffer can hold.
*/
[[nodiscard]] constexpr std::size_t capacity() const;
///@}
///@name Modifiers
///@{
/**
* @brief Clear the buffer and reset internal indices.
*
* After calling clear(), empty() returns true and size() returns 0.
*/
void clear();
///@}
private:
///@name Data members
///@{
RingBuffer<T, CAPACITY> m_buffer{}; ///< Underlying ring buffer
///@}
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Try to append a value to the buffer. If the buffer is full,
* the value is discarded. Return true if the value was inserted,
* false if the buffer was full. */
template<class T, std::size_t CAPACITY>
bool CircularBuffer<T, CAPACITY>::push(const T &value)
{
if (m_buffer.full())
return false;
return m_buffer.push(value);
}
//--------------------------------------------------------------
/* Remove and return the oldest value from the buffer. If the
* buffer is empty, return std::nullopt */
template<class T, std::size_t CAPACITY>
std::optional<T> CircularBuffer<T, CAPACITY>::pop()
{
return m_buffer.pop();
}
//--------------------------------------------------------------
/* Remove the oldest value from the buffer and store it in
* 'value'. Return true if successful, false if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool CircularBuffer<T, CAPACITY>::pop(T &value)
{
return m_buffer.pop(value);
}
//--------------------------------------------------------------
/* Return the oldest value without removing it. If the buffer is
* empty, return std::nullopt */
template<class T, std::size_t CAPACITY>
std::optional<T> CircularBuffer<T, CAPACITY>::front() const
{
return m_buffer.front();
}
//--------------------------------------------------------------
/* Return the oldest value without removing it and store it in
* 'value'. Return true if successful, false if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool CircularBuffer<T, CAPACITY>::front(T &value) const
{
return m_buffer.front(value);
}
//--------------------------------------------------------------
/* Return the newest value without removing it. If the buffer is
* empty, return std::nullopt */
template<class T, std::size_t CAPACITY>
std::optional<T> CircularBuffer<T, CAPACITY>::back() const
{
return m_buffer.back();
}
//--------------------------------------------------------------
/* Return the newest value without removing it and store it in
* 'value'. Return true if successful, false if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool CircularBuffer<T, CAPACITY>::back(T &value) const
{
return m_buffer.back(value);
}
//--------------------------------------------------------------
/* Check if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool CircularBuffer<T, CAPACITY>::empty() const
{
return m_buffer.empty();
}
//--------------------------------------------------------------
/* Check if the buffer is full */
template<class T, std::size_t CAPACITY>
bool CircularBuffer<T, CAPACITY>::full() const
{
return m_buffer.full();
}
//--------------------------------------------------------------
/* Get the number of elements currently in the buffer */
template<class T, std::size_t CAPACITY>
std::size_t CircularBuffer<T, CAPACITY>::size() const
{
return m_buffer.size();
}
//--------------------------------------------------------------
/* Get the maximum capacity of the buffer */
template<class T, std::size_t CAPACITY>
[[nodiscard]] constexpr std::size_t CircularBuffer<T, CAPACITY>::capacity() const
{
return CAPACITY;
}
//--------------------------------------------------------------
/* Clear the buffer, resetting it to an empty state */
template<class T, std::size_t CAPACITY>
void CircularBuffer<T, CAPACITY>::clear()
{
m_buffer.clear();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::common::utils

View File

@@ -0,0 +1,198 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <bit>
#include <cstdint>
#include <span>
#include <string_view>
namespace sdi_toolBox::common::utils::hash
{
//--------------------------------------------------------------
/**
* @brief Compute the FNV-1a 32-bit hash for a span of bytes.
*
* Primary API: accepts a `std::span<const std::byte>` so callers can pass any
* contiguous buffer without copies.
*
* @param data Span of bytes to hash.
* @return std::uint32_t 32-bit FNV-1a hash.
*/
constexpr std::uint32_t fnv1a(std::span<const std::byte> data) noexcept;
/**
* @brief Compute the FNV-1a 32-bit hash for a std::string_view.
*
* Convenience overload forwarding to the byte-based implementation.
*
* @param sv Input string view.
* @return std::uint32_t The 32-bit FNV-1a hash.
*/
constexpr std::uint32_t fnv1a(std::string_view sv) noexcept;
/**
* @brief Compute the FNV-1a 32-bit hash for a trivially copyable POD object.
*
* The object is hashed as its raw bytes (binary representation).
* Use only for POD/trivially-copyable types where this behaviour is intended.
*
* @tparam T Trivially copyable type.
* @param value Reference to the object to hash.
* @return std::uint32_t 32-bit FNV-1a hash.
*/
template<typename T>
requires std::is_trivially_copyable_v<T>
constexpr std::uint32_t fnv1a(const T &value) noexcept;
/**
* @brief Compute the FNV-1a 64-bit hash for a span of bytes.
*
* Primary API: accepts a `std::span<const std::byte>` so callers can pass any
* contiguous buffer without copies.
*
* @param data Span of bytes to hash.
* @return std::uint64_t 64-bit FNV-1a hash.
*/
constexpr std::uint64_t fnv1a_64(std::span<const std::byte> data) noexcept;
/**
* @brief Compute the FNV-1a 64-bit hash for a std::string_view.
*
* Convenience overload forwarding to the byte-based implementation.
*
* @param sv Input string view.
* @return std::uint64_t The 64-bit FNV-1a hash.
*/
constexpr std::uint64_t fnv1a_64(std::string_view sv) noexcept;
/**
* @brief Compute the FNV-1a 64-bit hash for a trivially copyable POD object.
*
* The object is hashed as its raw bytes (binary representation).
* Use only for POD/trivially-copyable types where this behaviour is intended.
*
* @tparam T Trivially copyable type.
* @param value Reference to the object to hash.
* @return std::uint64_t 64-bit FNV-1a hash.
*/
template<typename T>
requires std::is_trivially_copyable_v<T>
constexpr std::uint64_t fnv1a_64(const T &value) noexcept;
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Compute the FNV-1a 32-bit hash for a span of bytes */
inline constexpr std::uint32_t fnv1a(const std::span<const std::byte> data) noexcept
{
constexpr std::uint32_t FNV_OFFSET_BASIS = 2166136261u;
constexpr std::uint32_t FNV_PRIME = 16777619u;
std::uint32_t hash = FNV_OFFSET_BASIS;
for (const auto b : data)
{
hash ^= static_cast<std::uint32_t>(std::to_integer<std::uint8_t>(b));
hash *= FNV_PRIME;
}
return hash;
}
//--------------------------------------------------------------
/* Compute the FNV-1a 32-bit hash for a std::string_view */
inline constexpr std::uint32_t fnv1a(const std::string_view sv) noexcept
{
// Avoid reinterpret_cast on pointers: iterate characters (constexpr-friendly)
constexpr std::uint32_t FNV_OFFSET_BASIS = 2166136261u;
constexpr std::uint32_t FNV_PRIME = 16777619u;
std::uint32_t hash = FNV_OFFSET_BASIS;
for (char c : sv)
{
hash ^= static_cast<std::uint32_t>(static_cast<std::uint8_t>(c));
hash *= FNV_PRIME;
}
return hash;
}
//--------------------------------------------------------------
/* Compute the FNV-1a 32-bit hash for a trivially copyable POD object */
template<typename T>
requires std::is_trivially_copyable_v<T>
inline constexpr std::uint32_t fnv1a(const T &value) noexcept
{
// Use std::bit_cast to get bytes in a constexpr-friendly way
auto bytes = std::bit_cast<std::array<std::byte, sizeof(T)>>(value);
return fnv1a(std::span<const std::byte>(bytes.data(), bytes.size()));
}
//--------------------------------------------------------------
/* Compute the FNV-1a 64-bit hash for a span of bytes */
inline constexpr std::uint64_t fnv1a_64(const std::span<const std::byte> data) noexcept
{
constexpr std::uint64_t FNV_OFFSET_BASIS = 14695981039346656037ull;
constexpr std::uint64_t FNV_PRIME = 1099511628211ull;
std::uint64_t hash = FNV_OFFSET_BASIS;
for (const auto b : data)
{
hash ^= static_cast<std::uint64_t>(std::to_integer<std::uint8_t>(b));
hash *= FNV_PRIME;
}
return hash;
}
//--------------------------------------------------------------
/* Compute the FNV-1a 64-bit hash for a std::string_view */
inline constexpr std::uint64_t fnv1a_64(const std::string_view sv) noexcept
{
// Iterate characters to remain constexpr-friendly
constexpr std::uint64_t FNV_OFFSET_BASIS = 14695981039346656037ull;
constexpr std::uint64_t FNV_PRIME = 1099511628211ull;
std::uint64_t hash = FNV_OFFSET_BASIS;
for (char c : sv)
{
hash ^= static_cast<std::uint64_t>(static_cast<std::uint8_t>(c));
hash *= FNV_PRIME;
}
return hash;
}
//--------------------------------------------------------------
/* Compute the FNV-1a 64-bit hash for a trivially copyable POD object */
template<typename T>
requires std::is_trivially_copyable_v<T>
inline constexpr std::uint64_t fnv1a_64(const T &value) noexcept
{
// Use std::bit_cast to get bytes in a constexpr-friendly way
auto bytes = std::bit_cast<std::array<std::byte, sizeof(T)>>(value);
return fnv1a_64(std::span<const std::byte>(bytes.data(), bytes.size()));
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::common::utils::hash

View File

@@ -0,0 +1,358 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <array>
#include <cstddef>
#include <optional>
namespace sdi_toolBox::common::utils
{
//--------------------------------------------------------------
/**
* @brief Fixed-size ring (circular) buffer with compile-time capacity
* and overwrite-on-full behavior.
*
* This template implements a simple circular buffer with a compile-time
* fixed capacity. When the buffer is full and a new element is pushed,
* the oldest element is overwritten.
*
* @tparam T Element type stored in the buffer.
* @tparam CAPACITY Compile-time buffer capacity. Must be > 0.
*
* @note The class provides both optional-returning and out-parameter
* overloads for pop/front/back to suit different runtime constraints.
* @warning Instantiating with CAPACITY == 0 is forbidden (static_assert).
*
* @par Example:
* @code{.cpp}
* RingBuffer<int, 8> rb;
* rb.push(1);
* int v;
* if (rb.pop(v))
* {
* ...
* }
* @endcode
*/
template<class T, std::size_t CAPACITY>
class RingBuffer
{
static_assert(CAPACITY > 0, "CAPACITY must be > 0");
public:
///@name Write operations
///@{
/**
* @brief Append a value to the buffer.
*
* If the buffer is full, the oldest element is overwritten.
* @param value Value to append (copied).
*
* @return true if the value was added without overwriting, false if an overwrite occurred.
*/
bool push(const T &value);
///@}
///@name Read operations
///@{
/**
* @brief Remove and return the oldest element from the buffer.
*
* @return std::optional<T> The oldest element if present, std::nullopt if empty.
*/
std::optional<T> pop();
/**
* @brief Remove the oldest element and store it in the provided reference.
*
* @param value Output reference that receives the removed element.
*
* @return true if an element was removed, false if the buffer was empty.
*/
bool pop(T &value);
/**
* @brief Return (without removing) the oldest element.
*
* @return std::optional<T> The oldest element if present, std::nullopt if empty.
*/
[[nodiscard]] std::optional<T> front() const;
/**
* @brief Copy the oldest element into the provided reference without removing it.
*
* @param value Output reference that receives the element.
*
* @return true if the element was copied, false if the buffer is empty.
*/
[[nodiscard]] bool front(T &value) const;
/**
* @brief Return (without removing) the newest element.
*
* @return std::optional<T> The newest element if present, std::nullopt if empty.
*/
[[nodiscard]] std::optional<T> back() const;
/**
* @brief Copy the newest element into the provided reference without removing it.
*
* @param value Output reference that receives the element.
*
* @return true if the element was copied, false if the buffer is empty.
*/
[[nodiscard]] bool back(T &value) const;
///@}
///@name State queries
///@{
/**
* @brief Check whether the buffer is empty.
*
* @return true if empty, false otherwise.
*/
[[nodiscard]] bool empty() const;
/**
* @brief Check whether the buffer is full.
*
* @return true if full, false otherwise.
*/
[[nodiscard]] bool full() const;
/**
* @brief Number of elements currently stored in the buffer.
*
* @return Current size (0 .. CAPACITY).
*/
[[nodiscard]] std::size_t size() const;
/**
* @brief Compile-time capacity of the buffer.
*
* @return The maximum number of elements the buffer can hold.
*/
[[nodiscard]] constexpr std::size_t capacity() const;
///@}
///@name Modifiers
///@{
/**
* @brief Clear the buffer and reset internal indices.
*
* After calling clear(), empty() returns true and size() returns 0.
*/
void clear();
///@}
private:
///@name Internal helpers
///@{
/**
* @brief Compute the next index in a circular manner.
*
* Implemented to avoid expensive modulus operations on some targets.
* @param index Current index.
*
* @return Next index in range [0, CAPACITY-1].
*/
[[nodiscard]] std::size_t next(std::size_t index) const;
///@}
///@name Data members
///@{
std::array<T, CAPACITY> m_data{}; ///< Storage for the buffer elements
std::size_t m_head{ 0 }; ///< Index of the next element to write
std::size_t m_tail{ 0 }; ///< Index of the next element to read
bool m_full{ false }; ///< Indicates whether the buffer is full
///@}
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Append a value to the buffer. If the buffer is full, the
* oldest value will be overwritten. Return true if the value
* was added without overwriting, false if an overwrite occurred. */
template<class T, std::size_t CAPACITY>
bool RingBuffer<T, CAPACITY>::push(const T &value)
{
m_data[m_head] = value;
m_head = next(m_head);
if (m_full)
{
m_tail = m_head; // overwrite the oldest value
return false; // indicate that an overwrite occurred
}
else
{
m_full = (m_head == m_tail);
return true; // indicate that the value was added without overwriting
}
}
//--------------------------------------------------------------
/* Remove and return the oldest value from the buffer. If the
* buffer is empty, return std::nullopt */
template<class T, std::size_t CAPACITY>
std::optional<T> RingBuffer<T, CAPACITY>::pop()
{
if (empty())
return std::nullopt;
const std::optional<T> result = m_data[m_tail];
m_tail = next(m_tail);
m_full = false;
return result;
}
//--------------------------------------------------------------
/* Remove the oldest value from the buffer and store it in
* 'value'. Return true if successful, false if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool RingBuffer<T, CAPACITY>::pop(T &value)
{
if (empty())
return false;
value = m_data[m_tail];
m_tail = next(m_tail);
m_full = false;
return true;
}
//--------------------------------------------------------------
/* Return the oldest value without removing it. If the buffer is
* empty, return std::nullopt */
template<class T, std::size_t CAPACITY>
std::optional<T> RingBuffer<T, CAPACITY>::front() const
{
if (empty())
return std::nullopt;
return m_data[m_tail];
}
//--------------------------------------------------------------
/* Return the oldest value without removing it and store it in
* 'value'. Return true if successful, false if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool RingBuffer<T, CAPACITY>::front(T &value) const
{
if (empty())
return false;
value = m_data[m_tail];
return true;
}
//--------------------------------------------------------------
/* Return the newest value without removing it. If the buffer is
* empty, return std::nullopt */
template<class T, std::size_t CAPACITY>
std::optional<T> RingBuffer<T, CAPACITY>::back() const
{
if (empty())
return std::nullopt;
return m_data[(m_head == 0) ? CAPACITY - 1 : m_head - 1];
}
//--------------------------------------------------------------
/* Return the newest value without removing it and store it in
* 'value'. Return true if successful, false if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool RingBuffer<T, CAPACITY>::back(T &value) const
{
if (empty())
return false;
std::size_t backIndex = (m_head == 0) ? CAPACITY - 1 : m_head - 1;
value = m_data[backIndex];
return true;
}
//--------------------------------------------------------------
/* Check if the buffer is empty */
template<class T, std::size_t CAPACITY>
bool RingBuffer<T, CAPACITY>::empty() const
{
return !m_full && (m_head == m_tail);
}
//--------------------------------------------------------------
/* Check if the buffer is full */
template<class T, std::size_t CAPACITY>
bool RingBuffer<T, CAPACITY>::full() const
{
return m_full;
}
//--------------------------------------------------------------
/* Get the number of elements currently in the buffer */
template<class T, std::size_t CAPACITY>
std::size_t RingBuffer<T, CAPACITY>::size() const
{
if (m_full)
return CAPACITY;
if (m_head >= m_tail)
return m_head - m_tail;
return CAPACITY - (m_tail - m_head);
}
//--------------------------------------------------------------
/* Get the maximum capacity of the buffer */
template<class T, std::size_t CAPACITY>
[[nodiscard]] constexpr std::size_t RingBuffer<T, CAPACITY>::capacity() const
{
return CAPACITY;
}
//--------------------------------------------------------------
/* Clear the buffer, resetting it to an empty state */
template<class T, std::size_t CAPACITY>
void RingBuffer<T, CAPACITY>::clear()
{
m_head = 0;
m_tail = 0;
m_full = false;
}
//--------------------------------------------------------------
/* Helper function to calculate the next index in a circular manner */
template<class T, std::size_t CAPACITY>
std::size_t RingBuffer<T, CAPACITY>::next(const std::size_t index) const
{
// Calculate the next index in a circular manner without using
// modulus operator for better performance
const auto nextIndex = index + 1;
if (nextIndex == CAPACITY)
return 0;
return nextIndex;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::common::utils

View File

@@ -0,0 +1,529 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "defs.h"
#include "inode.h"
#include "message.h"
#include <chrono>
#include <memory>
#include <mutex>
#include <ranges>
#include <set>
#include <unordered_map>
namespace sdi_toolBox::desktop::eventBus
{
//--------------------------------------------------------------
/**
* @class Bus
* @brief Central message dispatcher for the event bus system.
*
* The Bus class is the core component of the event bus architecture.
* It manages a routing table that maps message type identifiers to lists
* of subscribed nodes, and dispatches messages to the appropriate nodes
* when they are emitted or posted.
*
* Nodes can subscribe to specific message types or to broadcast mode,
* in which case they receive all messages regardless of their type.
*
* The Bus is thread-safe: all operations on the routing table are
* protected by an internal mutex.
*
* @note The Bus is non-copyable and non-movable.
* @note The Bus does not take ownership of the nodes it manages.
*
* @par Example usage:
* @code
* sdi_toolBox::desktop::eventBus::Bus bus;
* sdi_toolBox::desktop::eventBus::Node node(bus);
*
* node.subscribe(MY_EVENT_TYPE);
* bus.emit<MyMessage>(arg1, arg2);
*
* auto msg = std::dynamic_pointer_cast<MyMessage>(node.popMessage());
* @endcode
*
* @see Node
* @see INode
* @see Message
*/
class Bus final
{
friend class Node; ///< Allow the Node class to access private members
using VectorNode = std::vector<INode *>;
public:
///@name Construction & Destruction
///@{
/**
* @brief Default constructor.
*
* Initializes the Bus and records the construction timestamp
* used as the bus start reference time.
*/
Bus();
/**
* @brief Default destructor.
*/
~Bus() = default;
/**
* @brief Copy constructor - deleted.
*
* The Bus is non-copyable.
*/
Bus(const Bus &obj) = delete;
/**
* @brief Move constructor - deleted.
*
* The Bus is non-movable.
*/
Bus(Bus &&obj) noexcept = delete;
/**
* @brief Copy assignment operator - deleted.
*
* The Bus is non-copyable.
*/
Bus &operator=(const Bus &obj) = delete;
/**
* @brief Move assignment operator - deleted.
*
* The Bus is non-movable.
*/
Bus &operator=(Bus &&obj) noexcept = delete;
///@}
///@name Subscription Management
///@{
/**
* @brief Remove all subscriptions from the routing table.
*
* Clears both the specific event subscriptions and the broadcast
* subscription list. After this call, no node will receive any message
* until it re-subscribes.
*
* @note This operation is thread-safe.
*/
void clearAllSubscriptions();
/**
* @brief Subscribe a node to a specific message type.
*
* Registers the given node to receive messages of the specified type.
* If the node is already subscribed to this type, this call has no effect
* (no duplicates are created).
*
* @param node Pointer to the node to subscribe. Must not be @c nullptr.
* @param eventType The message type identifier to subscribe to.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
void subscribe(INode *node, MessageTypeID eventType);
/**
* @brief Unsubscribe a node from a specific message type.
*
* Removes the given node from the list of subscribers for the specified
* message type. If the node was not subscribed to this type, this call
* has no effect.
*
* @param node Pointer to the node to unsubscribe. Must not be @c nullptr.
* @param eventType The message type identifier to unsubscribe from.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
void unsubscribe(INode *node, MessageTypeID eventType);
/**
* @brief Unsubscribe a node from all message types and broadcast mode.
*
* Removes the given node from all specific event subscription lists
* and from the broadcast subscription list. This is automatically called
* by the Node destructor to ensure no dangling pointers remain in the
* routing table.
*
* @param node Pointer to the node to unsubscribe. Must not be @c nullptr.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
void unsubscribeFromAll(INode *node);
/**
* @brief Check whether a node is subscribed to a specific message type.
*
* @param node Pointer to the node to check. Must not be @c nullptr.
* @param eventType The message type identifier to check.
* @return @c true if the node is subscribed to the given message type,
* @c false otherwise.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
[[nodiscard]] bool isSubscribed(const INode *node, MessageTypeID eventType) const;
///@}
///@name Broadcast management
///@{
/**
* @brief Subscribe a node to broadcast mode.
*
* A node in broadcast mode receives all messages posted to the bus,
* regardless of their type. A node can be subscribed to both broadcast
* mode and specific message types simultaneously, in which case it will
* receive the message twice for matching types.
*
* @param node Pointer to the node to subscribe. Must not be @c nullptr.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
void subscribeToBroadcast(INode *node);
/**
* @brief Unsubscribe a node from broadcast mode.
*
* Removes the given node from the broadcast subscription list.
* If the node was not subscribed to broadcast mode, this call has no effect.
*
* @param node Pointer to the node to unsubscribe. Must not be @c nullptr.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
void unsubscribeFromBroadcast(INode *node);
/**
* @brief Check whether a node is subscribed to broadcast mode.
*
* @param node Pointer to the node to check. Must not be @c nullptr.
* @return @c true if the node is subscribed to broadcast mode,
* @c false otherwise.
*
* @throws std::runtime_error if @p node is @c nullptr.
* @note This operation is thread-safe.
*/
[[nodiscard]] bool isSubscribedToBroadcast(INode *node) const; // Check if a node is subscribed to broadcast mode
///@}
///@name Message transmission
///@{
/**
* @brief Construct and emit a message of type @p T to the bus.
*
* Creates a new message of type @p T by forwarding the provided arguments
* to its constructor, then posts it to the bus via @ref post().
*
* @tparam T The message type to emit. Must be derived from @ref Message.
* @tparam Args Constructor argument types for @p T.
* @param args Arguments forwarded to the constructor of @p T.
* @return @c true if at least one subscriber received the message,
* @c false otherwise.
*
* @note Enforced at compile time: @p T must derive from @ref Message.
* @note This operation is thread-safe.
*
* @par Example:
* @code
* bus.emit<MyMessage>(arg1, arg2);
* @endcode
*/
template<class T, class... Args>
bool emit(Args &&...args);
/**
* @brief Post an already constructed message to the bus.
*
* Updates the message timestamp and dispatches it to all nodes subscribed
* to the message type, as well as all nodes in broadcast mode.
*
* @param message Shared pointer to the message to post. Must not be @c nullptr.
* @return @c true if at least one specific subscriber received the message,
* @c false otherwise.
*
* @note This operation is thread-safe.
* @see emit()
*/
bool post(const std::shared_ptr<Message> &message) const; // Post a message to the bus
///@}
private:
/**
* @brief Dispatch a message to all nodes subscribed to its type.
* @param eventType The message type identifier.
* @param message The message to dispatch.
* @return The number of nodes that received the message.
*/
size_t postMessageToSubscribers(MessageTypeID eventType, const std::shared_ptr<Message> &message) const;
/**
* @brief Dispatch a message to all nodes in broadcast mode.
* @param message The message to dispatch.
* @return The number of broadcast nodes that received the message.
*/
size_t postMessageToBroadcastSubscribers(const std::shared_ptr<Message> &message) const;
/**
* @brief Retrieve the subscriber list for a given message type.
* @param messageType The message type identifier.
* @return Pointer to the vector of subscribed nodes, or @c nullptr if none.
*/
const VectorNode *getSubscribersForMessageType(MessageTypeID messageType) const; // Get the list of subscribers for a specific message type
/// @brief Timestamp recorded when the Bus was constructed.
TimePoint m_busStartTimestamp;
/// @brief Internal routing table, protected by a mutex for thread-safe access.
struct
{
mutable std::mutex mtx; ///< Mutex for thread-safe access to the nodes map
std::unordered_map<MessageTypeID, VectorNode> nodeList; ///< Map of message type IDs to lists of subscribed nodes
std::set<INode *> broadcastNodeList; ///< Set of nodes subscribed to receive all messages (broadcast mode)
} m_routingTable;
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Default constructor */
inline Bus::Bus()
{
// Initialization
m_busStartTimestamp = std::chrono::steady_clock::now();
}
//--------------------------------------------------------------
/* Clear all subscriptions from the bus (remove all nodes from the routing table) */
inline void Bus::clearAllSubscriptions()
{
std::scoped_lock lock(m_routingTable.mtx);
m_routingTable.nodeList.clear(); // Clear all event subscriptions
m_routingTable.broadcastNodeList.clear(); // Clear all broadcast subscriptions
}
//--------------------------------------------------------------
/* Subscribe a listener to a specific event type */
inline void Bus::subscribe(INode *node, MessageTypeID eventType)
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
if (!m_routingTable.nodeList.contains(eventType)) // Event type not registered yet, create a new entry with the node
{
m_routingTable.nodeList[eventType] = { node };
}
else // Event type already registered, add the node if not already subscribed
{
auto &nodes = m_routingTable.nodeList.at(eventType);
if (std::ranges::find(nodes, node) == nodes.end())
{
// Node not already subscribed, add it to the list
nodes.push_back(node);
}
}
}
//--------------------------------------------------------------
/* Unsubscribe a listener from a specific event type */
inline void Bus::unsubscribe(INode *node, MessageTypeID eventType)
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Event type not recorded, nothing to do
if (!m_routingTable.nodeList.contains(eventType))
return;
// Event type registered, remove the listener if subscribed
auto &nodes = m_routingTable.nodeList.at(eventType);
std::erase(nodes, node);
}
//--------------------------------------------------------------
/* Unsubscribe a listener from a specific event type */
inline void Bus::unsubscribeFromAll(INode *node)
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Iterate through all event types and remove the node from each list
for (auto &nodeList : m_routingTable.nodeList | std::views::values)
std::erase(nodeList, node);
// Remove from broadcast subscriptions as well
m_routingTable.broadcastNodeList.erase(node);
}
//--------------------------------------------------------------
/* Check if a listener is subscribed to a specific event type */
inline bool Bus::isSubscribed(const INode *node, MessageTypeID eventType) const
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Event type not recorded, node not subscribed
if (!m_routingTable.nodeList.contains(eventType))
return false;
// Event type recorded, check if the node is in the list
const auto &nodes = m_routingTable.nodeList.at(eventType);
return std::ranges::find(nodes, node) != nodes.end();
}
//--------------------------------------------------------------
/* Subscribe a node to receive all messages (broadcast mode) */
inline void Bus::subscribeToBroadcast(INode *node)
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Add the node to the broadcast nodes set
m_routingTable.broadcastNodeList.insert(node);
}
//--------------------------------------------------------------
/* Unsubscribe a node from broadcast mode */
inline void Bus::unsubscribeFromBroadcast(INode *node)
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Remove the node from the broadcast nodes set
m_routingTable.broadcastNodeList.erase(node);
}
//--------------------------------------------------------------
/* Check if a node is subscribed to broadcast mode */
inline bool Bus::isSubscribedToBroadcast(INode *node) const
{
// Sanity check
if (!node)
throw std::runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Check if the node is in the broadcast nodes set
return m_routingTable.broadcastNodeList.contains(node);
}
//--------------------------------------------------------------
/* Emit a message of type T with the given arguments(create a message and post it to the bus) */
template<class T, class... Args>
bool Bus::emit(Args &&...args)
{
static_assert(std::derived_from<T, Message>, "T must be derived from IMessage");
// Create a message of type T with the given arguments
auto message = std::make_shared<T>(std::forward<Args>(args)...);
// Post the message to the bus
return post(message);
}
//--------------------------------------------------------------
/* Post a message to the bus */
inline bool Bus::post(const std::shared_ptr<Message> &message) const
{
std::scoped_lock lock(m_routingTable.mtx);
// Update message state
message->updateTimestamp(); // Update the timestamp of when the message was posted to the bus
// Post the message to all subscribers of the specific
// message type and get the number of subscribers that
// received the message
const auto subscriberCount = postMessageToSubscribers(message->getMessageTypeID(), message);
// Post the message to all broadcast subscribers and
// get the number of subscribers that received the
// message
(void)postMessageToBroadcastSubscribers(message);
return subscriberCount > 0;
}
//--------------------------------------------------------------
/* Post a message to all subscribers of a specific event type and return the number of subscribers that received the message */
inline size_t Bus::postMessageToSubscribers(const MessageTypeID eventType, const std::shared_ptr<Message> &message) const
{
const auto subscriberList = getSubscribersForMessageType(eventType);
if (!subscriberList)
return 0;
for (const auto &node : *subscriberList)
node->append(message);
return subscriberList->size();
}
//--------------------------------------------------------------
/* Post a message to all broadcast subscribers and return the number of subscribers that received the message */
inline size_t Bus::postMessageToBroadcastSubscribers(const std::shared_ptr<Message> &message) const
{
for (const auto &node : m_routingTable.broadcastNodeList)
node->append(message);
return m_routingTable.broadcastNodeList.size();
}
//--------------------------------------------------------------
/* Get the list of subscribers for a specific message type */
inline const Bus::VectorNode *Bus::getSubscribersForMessageType(const MessageTypeID messageType) const
{
if (!m_routingTable.nodeList.contains(messageType))
return nullptr; // No subscribers for this message type
return &m_routingTable.nodeList.at(messageType);
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::eventBus

View File

@@ -0,0 +1,45 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <chrono>
#include <cstdint>
//--------------------------------------------------------------
namespace sdi_toolBox::desktop::eventBus
{
using MessageTypeID = uint64_t; ///< Unique identifier for a message type
using TimePoint = std::chrono::steady_clock::time_point; ///< Monotonic timestamp type used throughout the event bus
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::eventBus

View File

@@ -0,0 +1,128 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "message.h"
namespace sdi_toolBox::desktop::eventBus
{
//--------------------------------------------------------------
/**
* @class INode
* @brief Abstract interface representing a subscriber node in the event bus system.
*
* INode is the base interface that all subscriber nodes must implement to
* participate in the event bus. It exposes a single private pure virtual method,
* @ref append(), which is called exclusively by the @ref Bus when a message is
* dispatched to this node.
*
* The @ref Bus is declared as a friend class to allow it to invoke @ref append()
* without exposing it to the rest of the codebase, enforcing a strict
* encapsulation of the message delivery mechanism.
*
* @note INode is non-copyable and non-movable.
* @note Direct instantiation is not possible - this class must be subclassed.
* The concrete implementation is provided by @ref Node.
*
* @see Bus
* @see Node
* @see Message
*/
class INode
{
friend class Bus; ///< Allow the Bus class to access private members
public:
///@name Construction & Destruction
///@{
/**
* @brief Default constructor.
*/
INode() = default;
/**
* @brief Default destructor.
*/
virtual ~INode() = default;
/**
* @brief Copy constructor - deleted.
*
* INode is non-copyable.
*/
INode(const INode &obj) = delete;
/**
* @brief Move constructor - deleted.
*
* INode is non-movable.
*/
INode(INode &&obj) noexcept = delete;
/**
* @brief Copy assignment operator - deleted.
*
* INode is non-copyable.
*/
INode &operator=(const INode &obj) = delete;
/**
* @brief Move assignment operator - deleted.
*
* INode is non-movable.
*/
INode &operator=(INode &&obj) noexcept = delete;
///@}
private:
/**
* @brief Insert a message into the node's internal message queue.
*
* This method is called exclusively by the @ref Bus when a message matching
* this node's subscriptions (or a broadcast message) is dispatched.
* It must be implemented by all concrete subclasses to define how incoming
* messages are stored or processed.
*
* @param message Shared pointer to the message being delivered.
*
* @note This method is intentionally private and only accessible to @ref Bus
* via the friend declaration, preventing external code from injecting
* messages directly into a node.
*/
virtual void append(const std::shared_ptr<Message> &message) = 0;
};
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::eventBus

View File

@@ -0,0 +1,195 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "defs.h"
namespace sdi_toolBox::desktop::eventBus
{
//--------------------------------------------------------------
/**
* @class Message
* @brief Base class for all messages dispatched through the event bus.
*
* Every message circulating in the event bus system must derive from this class.
* It carries a unique message type identifier (@ref MessageTypeID) used by the
* @ref Bus to route the message to the appropriate subscribers, and a timestamp
* that is updated when the message is posted to the bus.
*
* @note The Message class is non-copyable and non-movable.
* @note The default constructor is deleted: a @ref MessageTypeID must always
* be provided at construction time.
* @note The timestamp is set by the @ref Bus internally when @ref Bus::post()
* is called; it is not set at construction time.
*
* @par Example - defining a custom message:
* @code
* static constexpr sdi_toolBox::desktop::eventBus::MessageTypeID MY_EVENT = 1;
*
* struct MyMessage : public sdi_toolBox::desktop::eventBus::Message
* {
* explicit MyMessage(int value)
* : Message(MY_EVENT)
* , payload(value)
* {}
* int payload{};
* };
* @endcode
*
* @see Bus
* @see MessageTypeID
* @see TimePoint
*/
class Message
{
friend class Bus; ///< Allow the Bus class to access private members
public:
///@name Construction & Destruction
///@{
/**
* @brief Default constructor - deleted.
*
* A @ref MessageTypeID must always be provided at construction time.
*/
Message() = delete;
/**
* @brief Default destructor.
*/
virtual ~Message() = default;
/**
* @brief Copy constructor - deleted.
*
* Message is non-copyable.
*/
Message(const Message &obj) = delete;
/**
* @brief Move constructor - deleted.
*
* Message is non-movable.
*/
Message(Message &&obj) noexcept = delete;
/**
* @brief Copy assignment operator - deleted.
*
* Message is non-copyable.
*/
Message &operator=(const Message &obj) = delete;
/**
* @brief Move assignment operator - deleted.
*
* Message is non-movable.
*/
Message &operator=(Message &&obj) noexcept = delete;
/**
* @brief Construct a message with the given type identifier.
*
* @param messageTypeID Unique identifier representing the type of this message.
* Used by the @ref Bus to route the message to the correct
* subscribers.
*/
explicit Message(MessageTypeID messageTypeID);
///@}
///@name Accessors
///@{
/**
* @brief Get the unique type identifier of this message.
*
* @return The @ref MessageTypeID assigned at construction time.
*/
[[nodiscard]] MessageTypeID getMessageTypeID() const;
/**
* @brief Get the timestamp of when this message was posted to the bus.
*
* The timestamp is recorded by the @ref Bus when @ref Bus::post() is called.
* It is left at its default-constructed (zero) value if the message has not
* yet been posted.
*
* @return A @ref TimePoint representing the moment the message was dispatched.
* @see Bus::post()
*/
[[nodiscard]] TimePoint getTimestamp() const;
///@}
private:
/**
* @brief Update the message timestamp to the current time.
*
* Called internally by @ref Bus::post() just before the message is dispatched
* to subscribers. Not accessible from outside the bus.
*/
void updateTimestamp();
MessageTypeID m_messageTypeID; ///< Unique identifier for the message type
TimePoint m_messagePostTimestamp{}; ///< Timestamp of when the message was posted to the bus
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Constructor */
inline Message::Message(const MessageTypeID messageTypeID)
{
m_messageTypeID = messageTypeID;
}
//--------------------------------------------------------------
/* Get the unique identifier for the message type */
inline MessageTypeID Message::getMessageTypeID() const
{
return m_messageTypeID;
}
//--------------------------------------------------------------
/* Get the timestamp of when the message was posted to the bus */
inline TimePoint Message::getTimestamp() const
{
return m_messagePostTimestamp;
}
//--------------------------------------------------------------
/* Update the timestamp of when the message was posted to the bus */
inline void Message::updateTimestamp()
{
m_messagePostTimestamp = std::chrono::steady_clock::now();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::eventBus

View File

@@ -0,0 +1,390 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "bus.h"
#include "inode.h"
#include <mutex>
#include <queue>
namespace sdi_toolBox::desktop::eventBus
{
//--------------------------------------------------------------
/**
* @class Node
* @brief Concrete subscriber node in the event bus system.
*
* Node is the concrete implementation of @ref INode. It represents a participant
* in the event bus that can subscribe to specific message types or to broadcast
* mode, emit and post messages through the bus, and consume received messages
* from its internal FIFO queue.
*
* Each Node holds a reference to the @ref Bus it belongs to. Subscriptions and
* message transmissions are delegated to the bus. Incoming messages are stored
* in an internal thread-safe queue and can be retrieved via @ref popMessage().
*
* The Node also supports synchronous waiting: a thread can block on
* @ref syncWaitForMessage() until at least one message is available in the queue.
*
* On destruction, the Node automatically unsubscribes from all event types and
* broadcast mode, preventing dangling pointers in the bus routing table.
*
* @note Node is non-copyable and non-movable.
* @note A @ref Bus reference must be provided at construction time.
* @note The Node does not take ownership of the @ref Bus.
*
* @par Example usage:
* @code
* sdi_toolBox::desktop::eventBus::Bus bus;
* sdi_toolBox::desktop::eventBus::Node node(bus);
*
* node.subscribe(MY_EVENT_TYPE);
* node.emit<MyMessage>(42);
*
* node.syncWaitForMessage();
* auto msg = std::dynamic_pointer_cast<MyMessage>(node.popMessage());
* @endcode
*
* @see Bus
* @see INode
* @see Message
*/
class Node : public INode
{
public:
///@name Construction & Destruction
///@{
Node() = delete; ///< Default constructor - deleted. A @ref Bus reference must be provided.
/**
* @brief Destructor.
*
* Automatically unsubscribes the node from all specific event types and
* broadcast mode via @ref unsubscribeFromAll(), preventing dangling pointers
* in the bus routing table. Also notifies any thread blocked in
* @ref syncWaitForMessage() to unblock it gracefully.
*/
virtual ~Node();
/**
* @brief Copy constructor - deleted.
*
* Node is non-copyable.
*/
Node(const Node &obj) = delete;
/**
* @brief Move constructor - deleted.
*
* Node is non-movable.
*/
Node(Node &&obj) noexcept = delete;
/**
* @brief Copy assignment operator - deleted.
*
* Node is non-copyable.
*/
Node &operator=(const Node &obj) = delete;
/**
* @brief Move assignment operator - deleted.
*
* Node is non-movable.
*/
Node &operator=(Node &&obj) noexcept = delete;
/**
* @brief Construct a Node attached to the given @ref Bus.
*
* @param bus Reference to the @ref Bus this node belongs to.
* The bus must outlive the node.
*/
explicit Node(Bus &bus);
///@}
///@name Synchronization
///@{
/**
* @brief Block the calling thread until a message is received.
*
* Suspends the calling thread using an atomic wait until at least one message
* has been appended to the node's internal queue by the @ref Bus. This method
* is intended for synchronous event-driven patterns where a thread should idle
* until work is available.
*
* @note Returns immediately if a message is already pending in the queue
* at the time of the call.
* @note If the Node is destroyed while a thread is blocked here, the destructor
* triggers a notification to unblock the waiting thread gracefully.
* @warning The caller is responsible for checking the queue after this call
* returns, as the notification may also be triggered by the destructor
* with an empty queue.
*/
void syncWaitForMessage();
///@}
///@name Subscription Management
///@{
/**
* @brief Subscribe this node to a specific message type.
*
* Delegates to @ref Bus::subscribe(). The node will receive all messages
* of the given type posted to the bus. Duplicate subscriptions are ignored.
*
* @param eventType The message type identifier to subscribe to.
* @see Bus::subscribe()
*/
void subscribe(MessageTypeID eventType);
/**
* @brief Unsubscribe this node from a specific message type.
*
* Delegates to @ref Bus::unsubscribe(). If the node was not subscribed
* to the given type, this call has no effect.
*
* @param eventType The message type identifier to unsubscribe from.
* @see Bus::unsubscribe()
*/
void unsubscribe(MessageTypeID eventType);
/**
* @brief Unsubscribe this node from all message types and broadcast mode.
*
* Delegates to @ref Bus::unsubscribeFromAll(). After this call, the node
* will no longer receive any messages until it re-subscribes.
*
* @see Bus::unsubscribeFromAll()
*/
void unsubscribeFromAll();
///@}
///@name Message Transmission
///@{
/**
* @brief Construct and emit a message of type @p T through the bus.
*
* Forwards the call to @ref Bus::emit(). Creates a new message of type @p T
* using the provided arguments and posts it to the bus.
*
* @tparam T The message type to emit. Must be derived from @ref Message.
* @tparam Args Constructor argument types for @p T.
* @param args Arguments forwarded to the constructor of @p T.
* @return @c true if at least one subscriber received the message,
* @c false otherwise.
*
* @see Bus::emit()
*/
template<class T, class... Args>
bool emit(Args &&...args);
/**
* @brief Post an already constructed message through the bus.
*
* Forwards the call to @ref Bus::post().
*
* @param message Shared pointer to the message to post. Must not be @c nullptr.
* @return @c true if at least one subscriber received the message,
* @c false otherwise.
*
* @see Bus::post()
*/
bool post(const std::shared_ptr<Message> &message) const;
/**
* @brief Notify the node that a message has been received.
*
* Sets the atomic waiting flag to @c true and triggers a wake-up for any
* thread blocked in @ref syncWaitForMessage(). Has no effect if the flag
* is already set.
*/
void messageNotify();
///@}
///@name Message queue management
///@{
/**
* @brief Get the number of messages currently in the node's queue.
*
* @return The number of pending messages waiting to be consumed.
* @note This operation is thread-safe.
*/
size_t getMessageCount() const;
/**
* @brief Remove and return the front message from the node's queue (FIFO).
*
* Retrieves the oldest message in the queue and removes it. If the queue
* is empty, returns @c nullptr.
*
* @return A shared pointer to the front @ref Message, or @c nullptr if the
* queue is empty.
* @note This operation is thread-safe.
*/
std::shared_ptr<Message> popMessage(); // Pop a message from the node's message queue (remove and return the front message)
///@}
private:
/**
* @brief Append a message to the node's internal queue.
*
* Called exclusively by @ref Bus when dispatching a message to this node.
* Pushes the message onto the queue and notifies any thread waiting in
* @ref syncWaitForMessage().
*
* @param message Shared pointer to the message being delivered.
*/
void append(const std::shared_ptr<Message> &message) override;
Bus &m_bus; ///< Reference to the event bus
std::atomic_bool m_nodeWaitingFlag{ false }; ///< Flag to indicate if the node is waiting for a message (used for synchronous waiting)
/// @brief Internal message queue, protected by a mutex for thread-safe access.
struct
{
mutable std::mutex mtx; ///< Mutex for thread-safe access to the message queue
std::queue<std::shared_ptr<Message>> messageQueue; ///< Queue of messages received by the node
} m_busMessages;
};
//--------------------------------------------------------------
/* Constructor */
inline Node::Node(Bus &bus)
: m_bus(bus)
{
// Nothing to do here
}
//--------------------------------------------------------------
/* Default destructor */
inline Node::~Node()
{
// Unsubscribe from all event types when the node is destroyed
unsubscribeFromAll();
// Notify the bus that the node is being destroyed (in case it is waiting for a message)
messageNotify();
}
//--------------------------------------------------------------
/* Synchronously wait for a message to be posted to the bus and received by the node (block the calling thread until a message is received) */
inline void Node::syncWaitForMessage()
{
// Wait until at least one message is received in the node's
// message queue
m_nodeWaitingFlag = false;
m_nodeWaitingFlag.wait(false);
}
//--------------------------------------------------------------
/* Subscribe to receive messages of a specific event type */
inline void Node::subscribe(const MessageTypeID eventType)
{
m_bus.subscribe(this, eventType);
}
//--------------------------------------------------------------
/* Unsubscribe from receiving messages of a specific event type */
inline void Node::unsubscribe(const MessageTypeID eventType)
{
m_bus.unsubscribe(this, eventType);
}
//--------------------------------------------------------------
/* Unsubscribe from receiving messages of all event types */
inline void Node::unsubscribeFromAll()
{
m_bus.unsubscribeFromAll(this);
}
//--------------------------------------------------------------
/* Emit a message of type T with the given arguments (create a message and post it to the bus) */
template<class T, class... Args>
bool Node::emit(Args &&...args)
{
return m_bus.emit<T>(std::forward<Args>(args)...);
}
//--------------------------------------------------------------
/* Post a message to the bus */
inline bool Node::post(const std::shared_ptr<Message> &message) const
{
return m_bus.post(message);
}
//--------------------------------------------------------------
/* Get the number of messages in the node's message queue */
inline size_t Node::getMessageCount() const
{
std::scoped_lock lock(m_busMessages.mtx);
return m_busMessages.messageQueue.size();
}
//--------------------------------------------------------------
/* Pop a message from the node's message queue (remove and return the front message) */
inline std::shared_ptr<Message> Node::popMessage()
{
std::scoped_lock lock(m_busMessages.mtx);
if (m_busMessages.messageQueue.empty())
return nullptr; // No messages in the queue
auto message = m_busMessages.messageQueue.front(); // Get the front message
m_busMessages.messageQueue.pop(); // Remove the front message from the queue
return message; // Return the popped message
}
//--------------------------------------------------------------
/* Insert a message into the node's message queue (called by the bus when a message is posted to the bus) */
inline void Node::append(const std::shared_ptr<Message> &message)
{
std::scoped_lock lock(m_busMessages.mtx);
m_busMessages.messageQueue.push(message);
// If the node is waiting for a message, notify it that a
// message has been received
messageNotify();
}
//--------------------------------------------------------------
/* Notify the node that a message has been received (used for synchronous waiting) */
inline void Node::messageNotify()
{
if (!m_nodeWaitingFlag.load())
{
m_nodeWaitingFlag = true;
m_nodeWaitingFlag.notify_one();
}
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::eventBus

View File

@@ -0,0 +1,121 @@
/*
{{copyright}}
*/
/*
{{version}}
*/
/*
{{license}}
*/
#pragma once
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN // Disable messy Windows headers, which can cause conflicts with other libraries and increase compilation time
# endif
# include <Windows.h>
# include <iostream>
# include <memory>
namespace sdi_toolBox::desktop::msw
{
//--------------------------------------------------------------
class Win32Console
{
public:
static void initConsole(); // Init application console and attach debug console under MSW
static void releaseConsole(); // Release application console
public:
virtual ~Win32Console(); // Default destructor
Win32Console(const Win32Console &obj) = delete; // Copy constructor
Win32Console(Win32Console &&obj) noexcept = delete; // Move constructor
Win32Console &operator=(const Win32Console &obj) = delete; // Copy assignment operator
Win32Console &operator=(Win32Console &&obj) noexcept = delete; // Move assignment operator
static bool hasAttachedConsole(); // Returns true if console is attached, false otherwise
protected:
static inline std::unique_ptr<Win32Console> m_singleton;
Win32Console(); // Default constructor
FILE *m_stdoutFile; // Reopened stdout file pointer
FILE *m_stderrFile; // Reopened stderr file pointer
};
//--------------------------------------------------------------
/* Init application console and attach debug console under MSW */
inline void Win32Console::initConsole()
{
if (!m_singleton)
m_singleton = std::unique_ptr<Win32Console>(new Win32Console());
}
//--------------------------------------------------------------
/* Release application console */
inline void Win32Console::releaseConsole()
{
if (m_singleton)
m_singleton.reset();
}
//--------------------------------------------------------------
/* Default constructor */
inline Win32Console::Win32Console()
{
bool consoleIsCreated = false;
// Try to attach application to the current console
AttachConsole(ATTACH_PARENT_PROCESS);
if (!GetConsoleWindow()) // No console was available
{
// Create console and attach application to it
if (!AllocConsole())
throw std::logic_error("Unable to attach application to debug console"); // Error during creating console
consoleIsCreated = true;
}
// Reopen stdout and stderr streams to console window
if (freopen_s(&m_stdoutFile, "CONOUT$", "w", stdout) != 0)
throw std::logic_error("Unable to reopen stdout"); // Error during reopen on stdout
if (freopen_s(&m_stderrFile, "CONOUT$", "w", stderr) != 0)
throw std::logic_error("Unable to reopen stderr"); // Error during reopen on stderr
std::cout.clear();
std::cerr.clear();
if (!consoleIsCreated)
std::cout << std::endl; // Add a new line if console was already existing
}
//--------------------------------------------------------------
/* Default destructor */
inline Win32Console::~Win32Console()
{
std::cout.clear();
std::cerr.clear();
// Free console
FreeConsole();
// Close reopened stdout and stderr streams
(void)fclose(m_stdoutFile);
(void)fclose(m_stderrFile);
}
//--------------------------------------------------------------
/* Returns true if console is attached, false otherwise */
inline bool Win32Console::hasAttachedConsole()
{
if (GetConsoleWindow())
return true;
return false;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::msw
#else
# error This calss is only available under Windows systems
#endif // defined WIN32

View File

@@ -0,0 +1,261 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <optional>
#include <span>
#include <string>
#include <vector>
namespace sdi_toolBox::desktop::utils
{
//--------------------------------------------------------------
/**
* @brief Utility class providing Base64 encoding and decoding functionality.
*
* @details This class implements the Base64 encoding scheme as defined in RFC 4648.
* All methods are static and the class is not meant to be instantiated.
* Padding is handled via the '=' character.
*
* @note The standard Base64 alphabet is used ('A-Z', 'a-z', '0-9', '+', '/').
*
* @par Example - Encoding binary data:
* @code{.cpp}
* std::vector<std::uint8_t> data = { 0x48, 0x65, 0x6C, 0x6C, 0x6F };
* std::string encoded = Base64::encode(data);
* // encoded == "SGVsbG8="
* @endcode
*
* @par Example - Encoding a string:
* @code{.cpp}
* std::string encoded = Base64::encode("Hello, World!");
* // encoded == "SGVsbG8sIFdvcmxkIQ=="
* @endcode
*
* @par Example - Decoding:
* @code{.cpp}
* auto result = Base64::decode_to_string("SGVsbG8sIFdvcmxkIQ==");
* if (result)
* std::cout << *result; // prints: Hello, World!
* @endcode
*/
class Base64
{
public:
/**
* @brief Deleted default constructor - this class is not meant to be instantiated.
*/
Base64() = delete;
///@name Encoding
///@{
/**
* @brief Encodes binary data into a Base64 string.
*
* @param data A span of bytes to encode.
* @return A Base64-encoded string, padded with `'='` characters if necessary.
*/
[[nodiscard]] static std::string encode(std::span<const std::uint8_t> data);
/**
* @brief Encodes a text string into a Base64 string.
*
* @param text The input string to encode.
* @return A Base64-encoded string, padded with `'='` characters if necessary.
*/
[[nodiscard]] static std::string encode(std::string_view text);
///@}
///@name Decoding
///@{
/**
* @brief Decodes a Base64 string into raw binary data.
*
* @param input The Base64-encoded string to decode. Must have a length
* that is a multiple of 4.
* @return A vector of decoded bytes, or `std::nullopt` if the input is
* not a valid Base64 string.
*/
[[nodiscard]] static std::optional<std::vector<std::uint8_t>> decode(std::string_view input);
/**
* @brief Decodes a Base64 string into a text string.
*
* @param input The Base64-encoded string to decode. Must have a length
* that is a multiple of 4.
* @return The decoded string, or `std::nullopt` if the input is not a
* valid Base64 string.
*/
[[nodiscard]] static std::optional<std::string> decode_to_string(std::string_view input);
///@}
private:
///@name Internal helpers
///@{
/**
* @brief Decodes a single Base64 character into its 6-bit value.
*
* @param c The Base64 character to decode.
* @return The 6-bit value corresponding to @p c, or `std::nullopt` if
* @p c is not a valid Base64 character.
*/
[[nodiscard]] static std::optional<std::uint8_t> decode_char(char c) noexcept;
///@}
///@name Constants
///@{
/** @brief The Base64 encoding lookup table (RFC 4648 standard alphabet). */
static constexpr std::string_view ENCODE_LOOKUP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/** @brief The padding character used in Base64 encoding. */
static constexpr char PADDING_CAR = '=';
///@}
};
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Encode binary data to Base64 string */
inline std::string Base64::encode(const std::span<const std::uint8_t> data)
{
std::string result;
result.reserve(((data.size() + 2) / 3) * 4);
for (std::size_t i = 0; i < data.size(); i += 3)
{
const std::uint32_t b0 = data[i];
const std::uint32_t b1 = (i + 1 < data.size()) ? data[i + 1] : 0u;
const std::uint32_t b2 = (i + 2 < data.size()) ? data[i + 2] : 0u;
const std::uint32_t triple = (b0 << 16) | (b1 << 8) | b2;
result += ENCODE_LOOKUP[(triple >> 18) & 0x3F];
result += ENCODE_LOOKUP[(triple >> 12) & 0x3F];
result += (i + 1 < data.size()) ? ENCODE_LOOKUP[(triple >> 6) & 0x3F] : PADDING_CAR;
result += (i + 2 < data.size()) ? ENCODE_LOOKUP[(triple >> 0) & 0x3F] : PADDING_CAR;
}
return result;
}
//--------------------------------------------------------------
/* Encode a text string to Base64 string */
inline std::string Base64::encode(const std::string_view text)
{
return encode(std::span(reinterpret_cast<const std::uint8_t *>(text.data()), text.size()));
}
//--------------------------------------------------------------
/* Decode a Base64 string to binary data (returns std::nullopt if invalid) */
inline std::optional<std::vector<std::uint8_t>> Base64::decode(const std::string_view input)
{
if (input.size() % 4 != 0)
return std::nullopt;
if (input.empty())
return std::vector<std::uint8_t>{};
// Validate padding: '=' is only allowed in the last group, in position 2 or 3
// Valid forms: "xxx=" or "xx=="
for (std::size_t i = 0; i < input.size() - 4; ++i)
{
if (input[i] == PADDING_CAR)
return std::nullopt; // '=' found outside the last group
}
const std::string_view last = input.substr(input.size() - 4);
// "xx==" : positions 0,1 must be valid chars, positions 2,3 must be '='
// "xxx=" : positions 0,1,2 must be valid chars, position 3 must be '='
// "xxxx" : all positions must be valid chars
if (last[2] == PADDING_CAR && last[3] != PADDING_CAR)
return std::nullopt; // "xx=x" is invalid
std::vector<std::uint8_t> result;
result.reserve((input.size() / 4) * 3);
for (std::size_t i = 0; i < input.size(); i += 4)
{
const auto v0 = decode_char(input[i]);
const auto v1 = decode_char(input[i + 1]);
const auto v2 = input[i + 2] == PADDING_CAR ? std::optional<std::uint8_t>{ 0 } : decode_char(input[i + 2]);
const auto v3 = input[i + 3] == PADDING_CAR ? std::optional<std::uint8_t>{ 0 } : decode_char(input[i + 3]);
if (!v0 || !v1 || !v2 || !v3)
return std::nullopt;
const std::uint32_t triple =
(static_cast<std::uint32_t>(*v0) << 18) |
(static_cast<std::uint32_t>(*v1) << 12) |
(static_cast<std::uint32_t>(*v2) << 6) |
(static_cast<std::uint32_t>(*v3));
result.push_back(static_cast<std::uint8_t>((triple >> 16) & 0xFF));
if (input[i + 2] != PADDING_CAR)
result.push_back(static_cast<std::uint8_t>((triple >> 8) & 0xFF));
if (input[i + 3] != PADDING_CAR)
result.push_back(static_cast<std::uint8_t>((triple >> 0) & 0xFF));
}
return result;
}
//--------------------------------------------------------------
/* Decode a Base64 string to a text string (returns std::nullopt if invalid) */
inline std::optional<std::string> Base64::decode_to_string(const std::string_view input)
{
const auto bytes = decode(input);
if (!bytes)
return std::nullopt;
return std::string(reinterpret_cast<const char *>(bytes->data()), bytes->size());
}
//--------------------------------------------------------------
/* Helper to decode a single Base64 character to its 6-bit value (returns std::nullopt if invalid) */
inline std::optional<std::uint8_t> Base64::decode_char(const char c) noexcept
{
if (c >= 'A' && c <= 'Z')
return static_cast<std::uint8_t>(c - 'A');
if (c >= 'a' && c <= 'z')
return static_cast<std::uint8_t>(c - 'a' + 26);
if (c >= '0' && c <= '9')
return static_cast<std::uint8_t>(c - '0' + 52);
if (c == '+')
return 62;
if (c == '/')
return 63;
return std::nullopt;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::utils

View File

@@ -0,0 +1,219 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <array>
#include <chrono>
#include <format>
#include <iomanip>
#include <random>
#include <sstream>
#include <string>
namespace sdi_toolBox::desktop::utils::uuid
{
//--------------------------------------------------------------
/**
* @brief Generate a unique identifier based on the current time (microseconds).
*
* Produces a string identifier derived from the current timestamp in microseconds.
* If @p moreEntropy is true, additional random bytes are appended to increase
* uniqueness (useful when multiple identifiers are generated in quick succession).
*
* @param moreEntropy If true, append extra random entropy to the identifier.
* @return std::string Generated identifier (ASCII string, format not strictly defined).
*/
std::string uniqid(bool moreEntropy = false);
/**
* @brief Generate a random UUID (version 4).
*
* Produces a RFC 4122 compliant UUID version 4 using random data.
* The returned string uses the canonical textual representation:
* "8-4-4-4-12" hexadecimal digits.
*
* @return std::string UUID v4 as a text string.
*/
std::string v4();
/**
* @brief Generate a time-ordered UUID (version 7).
*
* Produces a UUID v7 combining a timestamp and random bits to allow
* roughly time-ordered identifiers while keeping sufficient entropy for uniqueness.
* The returned string uses the canonical textual representation:
* "8-4-4-4-12" hexadecimal digits.
*
* @note UUID v7 is a newer specification intended for sortable UUIDs.
*
* @return std::string UUID v7 as a text string.
*/
std::string v7();
//--------------------------------------------------------------
//--------------------------------------------------------------
/* Generates a unique identifier based on the current time in
* microseconds. If the `moreEntropy` parameter is set to
* `true`, additional random data is appended to the identifier
* to increase its uniqueness. The generated identifier is
* returned as a string. */
inline std::string uniqid(const bool moreEntropy)
{
const auto now = std::chrono::system_clock::now(); // Get current time point
const auto epoch = now.time_since_epoch(); // Get duration since epoch
const auto us_since_epoch = std::chrono::duration_cast<std::chrono::microseconds>(epoch).count();
// PHP format: "%08x%05x" -> 8 hex chars (sec) + 5 hex chars (usec fraction)
// sec = full seconds since epoch
// usec = microseconds fraction within the current second (0..999999)
const auto sec = static_cast<uint32_t>(us_since_epoch / 1'000'000);
const auto usec = static_cast<uint32_t>(us_since_epoch % 1'000'000);
std::string result = std::format("{:08x}{:05x}", sec, usec);
// PHP moreEntropy: appends a random float in [0, 10) with 8 decimal places
// Example output: "5f68b2056fbac7.12345678"
if (moreEntropy)
{
thread_local auto rng = std::mt19937{ std::random_device{}() };
auto f_dist = std::uniform_real_distribution<double>(0.0, 10.0);
// Append the random data in hexadecimal format
result = result + std::format("{:.8f}", f_dist(rng));
}
return result;
}
//--------------------------------------------------------------
/* Generates a random UUID (version 4) and returns it as a
* string. The UUID is generated using random numbers, and it
* follows the standard format of 8-4-4-4-12 hexadecimal
* characters. */
inline std::string v4()
{
/*
* UUID version 4 is a randomly generated UUID.
* It's structured as follows:
* - 122 bits are random
* - 6 bits are fixed to indicate the version and variant
* The total is 128 bits, formatted as 8-4-4-4-12 hexadecimal characters.
*
* The version (4) is set in the 7th byte (index 6) and the variant
* is set in the 9th byte (index 8).
*/
std::array<uint8_t, 16> bytes;
// Fast thread-local RNG seeded from random_device once per thread
thread_local auto rng = std::mt19937{ std::random_device{}() };
auto dist = std::uniform_int_distribution<uint32_t>(0, 0xFF);
// Fill the random bytes
for (auto &byte : bytes)
byte = static_cast<uint8_t>(dist(rng)); // Generate random bytes (8 bits each)
// Set version to 4 -> xxxx0100 in the 7th byte (index 6)
bytes[6] = static_cast<uint8_t>((bytes[6] & 0x0F) | 0x40);
// Set variant to 10xxxxxx in the 9th byte (index 8)
bytes[8] = static_cast<uint8_t>((bytes[8] & 0x3F) | 0x80);
// Format as 8-4-4-4-12 hex
std::ostringstream oss;
oss << std::hex << std::nouppercase << std::setfill('0');
for (int i = 0; i < 16; ++i)
{
oss << std::setw(2) << static_cast<int>(bytes[i]);
if (i == 3 || i == 5 || i == 7 || i == 9)
oss << '-';
}
return oss.str();
}
//--------------------------------------------------------------
/* Generates a UUID (version 7) based on the current timestamp
* and random data. The UUID is generated using a combination of
* the current time and random numbers, and it follows the
* standard format of 8-4-4-4-12 hexadecimal characters. */
inline std::string v7()
{
/*
* UUID version 7 is a time-ordered UUID that combines a timestamp with random bits.
* It's structured as follows:
* - 48 bits for the timestamp (milliseconds since Unix epoch)
* - 12 bits for the version (7)
* - 62 bits for random data (to ensure uniqueness)
* The total is 128 bits, formatted as 8-4-4-4-12 hexadecimal characters.
*
* The version (7) is set in the 7th byte (index 6) and the variant
* is set in the 9th byte (index 8).
*/
std::array<uint8_t, 16> bytes;
// Fast thread-local RNG seeded from random_device once per thread
thread_local auto rng = std::mt19937{ std::random_device{}() };
auto dist = std::uniform_int_distribution<uint32_t>(0, 0xFF);
const auto now = std::chrono::system_clock::now(); // Get current time point
const auto epoch = now.time_since_epoch(); // Get duration since epoch
const uint64_t ms_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(epoch).count();
// Fill timestamp (Big-endian)
bytes[0] = static_cast<uint8_t>((ms_since_epoch >> 40) & 0xFF);
bytes[1] = static_cast<uint8_t>((ms_since_epoch >> 32) & 0xFF);
bytes[2] = static_cast<uint8_t>((ms_since_epoch >> 24) & 0xFF);
bytes[3] = static_cast<uint8_t>((ms_since_epoch >> 16) & 0xFF);
bytes[4] = static_cast<uint8_t>((ms_since_epoch >> 8) & 0xFF);
bytes[5] = static_cast<uint8_t>(ms_since_epoch & 0xFF);
// Generate randomness for the rest
for (size_t i = 6; i < 16; ++i)
bytes[i] = static_cast<uint8_t>(dist(rng)); // Generate random bytes (8 bits each)
// Set Version 7 (bits 48-51 -> 0111)
bytes[6] = static_cast<uint8_t>((bytes[6] & 0x0F) | 0x70);
// Set Variant (bits 64-65 -> 10)
bytes[8] = static_cast<uint8_t>((bytes[8] & 0x3F) | 0x80);
// Format as 8-4-4-4-12 hex
std::ostringstream oss;
oss << std::hex << std::nouppercase << std::setfill('0');
for (int i = 0; i < 16; ++i)
{
oss << std::setw(2) << static_cast<int>(bytes[i]);
if (i == 3 || i == 5 || i == 7 || i == 9)
oss << '-';
}
return oss.str();
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::utils::uuid

View File

@@ -0,0 +1,174 @@
/*
Copyright (c) 2026 - SD-Innovation S.A.S. - FRANCE
*/
/*
ver: 2.x.x - build: 2026-04-28
*/
/*
The zlib License
Copyright (c) 2026 SD-Innovation S.A.S.
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <vector>
#include <wx/wx.h>
namespace sdi_toolBox::desktop::wxWidgets
{
//--------------------------------------------------------------
/**
* @brief Manages a list of file wildcard entries for use in wxWidgets file dialogs.
*
* The Wildcard class provides a convenient way to build and format wildcard filter strings
* compatible with wxWidgets file dialogs (e.g., `wxFileDialog`).
*
* Each entry consists of a human-readable description and a file pattern (e.g., `*.txt`).
* The formatted string follows the wxWidgets wildcard format:
* @code{.cpp}
* "Description (*.ext)|*.ext|Other (*.other)|*.other"
* @endcode
*
* @par Example
* @code{.cpp}
* sdi_toolBox::desktop::wxWidgets::Wildcard wc;
* wc.addEntry("Text files", "*.txt");
* wc.addEntry("CSV files", "*.csv");
* wxString filter = wc.getWildcards(true); // includes "All files (*.*)|*.*"
* wxFileDialog dlg(this, "Open file", "", "", filter, wxFD_OPEN);
* @endcode
*/
class Wildcard
{
public:
/**
* @struct WildcardEntry
* @brief Represents a single wildcard filter entry.
*/
struct WildcardEntry
{
wxString description; ///< Human-readable description of the file type (e.g., "Text files").
wxString pattern; ///< File pattern used for filtering (e.g., "*.txt").
};
public:
/**
* @brief Default constructor.
*/
Wildcard() = default;
/**
* @brief Default destructor.
*/
virtual ~Wildcard() = default;
/**
* @brief Copy constructor.
* @param other The Wildcard instance to copy from.
*/
Wildcard(const Wildcard &other) = default;
/**
* @brief Move constructor.
* @param other The Wildcard instance to move from.
*/
Wildcard(Wildcard &&other) noexcept = default;
/**
* @brief Copy assignment operator.
* @param other The Wildcard instance to copy from.
* @return Reference to this instance.
*/
Wildcard &operator=(const Wildcard &other) = default;
/**
* @brief Move assignment operator.
* @param other The Wildcard instance to move from.
* @return Reference to this instance.
*/
Wildcard &operator=(Wildcard &&other) noexcept = default;
/**
* @brief Removes all wildcard entries.
*/
void clear();
/**
* @brief Adds a new wildcard filter entry.
* @param description Human-readable description of the file type (e.g., "Text files").
* @param pattern File pattern used for filtering (e.g., "*.txt").
*/
void addEntry(const wxString &description, const wxString &pattern);
/**
* @brief Builds and returns the formatted wildcard string for use in wxWidgets file dialogs.
* @param addAllFiles If @c true, appends an "All files (*.*)|*.*" entry at the end.
* @return A formatted wildcard string compatible with wxWidgets file dialog filters.
*/
[[nodiscard]] wxString getWildcards(bool addAllFiles = false) const;
protected:
std::vector<WildcardEntry> m_wildcards; // List of wildcard strings
};
//--------------------------------------------------------------
/* Clear all wildcard entries */
inline void Wildcard::clear()
{
m_wildcards.clear();
}
//--------------------------------------------------------------
/* Add a wildcard entry */
inline void Wildcard::addEntry(const wxString &description, const wxString &pattern)
{
m_wildcards.push_back({ .description = description, .pattern = pattern });
}
//--------------------------------------------------------------
/* Get wildcard list */
inline wxString Wildcard::getWildcards(const bool addAllFiles) const
{
wxString wildcardList;
// Add wildcard entries to the list
for (const auto &entry : m_wildcards)
{
if (!wildcardList.empty())
wildcardList += _T("|");
wildcardList += entry.description + _T(" (") + entry.pattern + _T(")|") + entry.pattern;
}
// Optionally add "All files" entry
if (addAllFiles)
{
if (!wildcardList.empty())
wildcardList += _T("|");
wildcardList += _("All files") + _T(" (") + _T("*.*") + _T(")|") + _T("*.*");
}
return wildcardList;
}
//--------------------------------------------------------------
} // namespace sdi_toolBox::desktop::wxWidgets

View File

@@ -0,0 +1,32 @@
#include "wxBusEvent.h"
wxDEFINE_EVENT(wx_BUSEVENT_MESSAGE, wxBusEvent);
//--------------------------------------------------------------
/* Constructor */
wxBusEvent::wxBusEvent(message_t message, const int id)
: wxCommandEvent(wx_BUSEVENT_MESSAGE, id)
, m_message(std::move(message))
{
// Nothing to do here
}
//--------------------------------------------------------------
/* Clone method for wxWidgets event system */
wxEvent *wxBusEvent::Clone() const
{
auto *clonedEvent = new wxBusEvent(m_message, GetId());
clonedEvent->SetEventObject(GetEventObject()); // Copy the event object
return clonedEvent;
}
//--------------------------------------------------------------
/* Get the message type ID */
wxBusEvent::MessageTypeID wxBusEvent::getMessageTypeID() const
{
return m_message ? m_message->getMessageTypeID() : 0; // Return 0 if message is null
}
//--------------------------------------------------------------
/* Get the message data */
wxBusEvent::message_t wxBusEvent::getMessage() const
{
return m_message;
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,33 @@
#pragma once
#include <sdi_toolBox/desktop/eventBus/defs.h>
#include <sdi_toolBox/desktop/eventBus/message.h>
#include <wx/wx.h>
//--------------------------------------------------------------
class wxBusEvent : public wxCommandEvent
{
using MessageTypeID = sdi_toolBox::desktop::eventBus::MessageTypeID;
using message_t = std::shared_ptr<sdi_toolBox::desktop::eventBus::Message>;
public:
wxBusEvent() = delete; // Default constructor
virtual ~wxBusEvent() = default; // Default destructor
wxBusEvent(const wxBusEvent &other) = default; // Copy constructor
wxBusEvent(wxBusEvent &&other) noexcept = default; // Move constructor
wxBusEvent &operator=(const wxBusEvent &other) = delete; // Copy assignment
wxBusEvent &operator=(wxBusEvent &&other) noexcept = delete; // Move assignment
explicit wxBusEvent(message_t message, int id = wxID_ANY); // Constructor
[[nodiscard]] wxEvent *Clone() const override; // Clone method for wxWidgets event system
// Data management
[[nodiscard]] MessageTypeID getMessageTypeID() const; // Get the message type ID
[[nodiscard]] message_t getMessage() const; // Get the message data
protected:
message_t m_message;
};
//--------------------------------------------------------------
wxDECLARE_EVENT(wx_BUSEVENT_MESSAGE, wxBusEvent);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,187 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_DIAGNOSTIC_POSITIONS
#define JSON_DIAGNOSTIC_POSITIONS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_DIAGNOSTIC_POSITIONS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

View File

@@ -0,0 +1,94 @@
#pragma once
// This file contains only Doxygen documentation for namespaces.
// No code should be added here.
/**
* @namespace sdi_toolBox
* @brief Root namespace for the sdi_toolBox library.
*/
/**
* @namespace sdi_toolBox::common
* @brief Cross-platform, reusable components shared across all sdi_toolBox modules.
*
* The common namespace groups utilities and building blocks that are not
* tied to any specific platform or subsystem (desktop, embedded, etc.)
* and can be freely used across the entire library.
*/
/**
* @namespace sdi_toolBox::common::utils
* @brief General-purpose utility functions shared across all sdi_toolBox modules.
*
* Provides lightweight, header-only helpers with no dependency on any
* specific platform or subsystem.
*/
/**
* @namespace sdi_toolBox::common::utils::hash
* @brief Lightweight hashing utilities (FNV-1a implementations).
*
* Provides constexpr, header-only implementations of the FNV-1a hashing
* algorithms (32-bit and 64-bit) with overloads that accept:
* - std::span<const std::byte>
* - std::string_view
* - trivially-copyable POD objects
*
* These utilities are intended for fast, deterministic hashing of byte
* sequences and simple value-based hashing of POD types. Implementations
* are usable in constexpr contexts for string literals and other compile-time
* scenarios.
*
* @note Use the POD overloads only for trivially-copyable types where
* hashing the raw memory representation is intended.
* @see fnv1a(), fnv1a_64()
*/
/**
* @namespace sdi_toolBox::desktop
* @brief Desktop-specific components of the sdi_toolBox library.
*/
/**
* @namespace sdi_toolBox::desktop::eventBus
* @brief Lightweight thread-safe event bus for decoupled message passing.
*
* The eventBus namespace provides a publish/subscribe messaging system that
* allows decoupled communication between components. It is built around three
* core concepts:
*
* - **Bus** : the central dispatcher that maintains a routing table and
* delivers messages to the appropriate subscribers.
* - **Node** : a subscriber attached to a Bus that can send and receive
* messages through its internal FIFO queue.
* - **Message** : the base class for all messages circulating in the bus,
* identified by a unique @ref MessageTypeID.
*
* Nodes can subscribe to specific message types or to broadcast mode.
* All operations are thread-safe.
*
* @see Bus
* @see Node
* @see Message
*/
/**
* @namespace sdi_toolBox::desktop::utils
* @brief General-purpose utility functions and helpers for desktop applications.
*/
/**
* @namespace sdi_toolBox::desktop::utils::uuid
* @brief Utility functions for UUID and unique identifier generation.
*
* Provides functions to generate unique identifiers in various formats:
* - @ref uniqid() : timestamp-based unique identifier (PHP-style).
* - @ref v4() : randomly generated UUID (RFC 4122 version 4).
* - @ref v7() : time-ordered UUID (RFC 4122 version 7).
*/
/**
* @namespace sdi_toolBox::desktop::wxWidgets
* @brief Namespace containing desktop UI utilities built on top of the wxWidgets framework.
*/