Initial commit of source code

This commit is contained in:
Sylvain Schneider
2026-03-12 16:32:03 +01:00
parent b5d0fef4d9
commit f25c5789ea
46 changed files with 27732 additions and 0 deletions

121
src/api/requirement.h Normal file
View File

@@ -0,0 +1,121 @@
#pragma once
#include <dbus/api/api.h>
#include <filesystem>
#include <sdi_toolBox/generic/uuid.h>
namespace api::requirement
{
//--------------------------------------------------------------
enum class FileOperation : uint8_t
{
Create = 0,
Open,
Save,
SaveAs
};
//--------------------------------------------------------------
struct Requirement
{
std::string uuid;
std::string parent_uuid;
std::string id;
std::string type;
std::string name;
std::string description;
std::vector<Requirement> sub_requirements;
/* Default constructor */
Requirement()
{
uuid = sdi_toolBox::generic::UuidGenerator::uniqid(); // Generate a unique identifier for the requirement
}
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
// Post event structs
//--------------------------------------------------------------
struct FileOperationEvent : dBus::api::DefaultData<dBus::makeID("requirement.file.operation")>
{
virtual ~FileOperationEvent() = default; // Default destructor
FileOperation fileOperation;
std::filesystem::path filePath;
[[nodiscard]] std::string toString() const override
{
if (fileOperation == FileOperation::Open || fileOperation == FileOperation::SaveAs)
return std::format("FileOperationEvent: operation={}, filePath={}", getOperationString(), filePath.string());
else
return std::format("FileOperationEvent: operation={}", getOperationString());
}
private:
[[nodiscard]] std::string getOperationString() const
{
switch (fileOperation)
{
using enum FileOperation;
case Create:
return "Create";
case Open:
return "Open";
case Save:
return "Save";
case SaveAs:
return "SaveAs";
default:
return "";
}
}
};
//--------------------------------------------------------------
struct RequirementEvent : dBus::api::DefaultData<dBus::makeID("requirement.requirementEvent")>
{
virtual ~RequirementEvent() = default; // Default destructor
Requirement requirementData;
[[nodiscard]] std::string toString() const override
{
return std::format("RequirementEvent: id={}, name={}, subRequirementsCount={}",
requirementData.id,
requirementData.name,
requirementData.description,
requirementData.sub_requirements.size());
}
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
// Post event helpers
//--------------------------------------------------------------
inline dBus::api::PostReturnStatus postFileOperationRequest(dBus::Bus &bus, const FileOperation &fileOperation, const std::filesystem::path &filePath = "")
{
auto eventData = FileOperationEvent();
eventData.fileOperation = std::move(fileOperation);
eventData.filePath = std::move(filePath);
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
/* Post a requirement event to the bus. This function will post a
* RequirementEvent containing the provided requirement data to the
* bus, and return a PostReturnStatus indicating whether the post was
* successful or if there were any issues (e.g., no subscribers,
* timeout, etc.). */
inline dBus::api::PostReturnStatus postRequirementRequest(dBus::Bus &bus,
Requirement requirement)
{
auto eventData = RequirementEvent();
eventData.requirementData = std::move(requirement);
return dBus::api::requestData<decltype(eventData), void>(bus, eventData.getID(), eventData, dBus::MessageCategory::UI);
}
//--------------------------------------------------------------
} // namespace api::requirement

45
src/core/coreManager.cpp Normal file
View File

@@ -0,0 +1,45 @@
#include "coreManager.h"
using namespace std;
using namespace core;
//--------------------------------------------------------------
/* Default constructor */
CoreManager::CoreManager()
{
init();
}
//--------------------------------------------------------------
/* Default destructor */
CoreManager::~CoreManager()
{
release();
}
//--------------------------------------------------------------
/* Get a reference to the application bus */
dBus::Bus &CoreManager::getBus() const
{
return *m_bus;
}
//--------------------------------------------------------------
/* Initialize the core manager (e.g., set up the bus, etc.) */
void CoreManager::init()
{
// Initialize the application bus
m_bus = make_unique<dBus::Bus>();
// Initialize other subsystems (e.g., logger, etc.)
m_logger = make_unique<logger::Logger>(*m_bus);
m_requirementManager = make_unique<requirementManager::RequirementManager>(*m_bus);
}
//--------------------------------------------------------------
/* Release resources used by the core manager (e.g., clean up the bus, etc.) */
void CoreManager::release()
{
// Clean up other subsystems (e.g., logger, etc.)
m_requirementManager.reset();
m_logger.reset();
// Clean up the application bus
m_bus.reset();
}
//--------------------------------------------------------------

35
src/core/coreManager.h Normal file
View File

@@ -0,0 +1,35 @@
#pragma once
#include "logger/logger.h"
#include "requirementManager/requirementManager.h"
#include <dBus/dBus.h>
#include <memory>
namespace core
{
//--------------------------------------------------------------
class CoreManager
{
public:
CoreManager(); // Default constructor
virtual ~CoreManager(); // Default destructor
CoreManager(const CoreManager &obj) = delete; // Copy constructor
CoreManager(CoreManager &&obj) noexcept = delete; // Move constructor
CoreManager &operator=(const CoreManager &obj) = delete; // Copy assignment operator
CoreManager &operator=(CoreManager &&obj) noexcept = delete; // Move assignment operator
[[nodiscard]] dBus::Bus &getBus() const; // Get a reference to the application bus
protected:
std::unique_ptr<dBus::Bus> m_bus; // Application data bus
std::unique_ptr<logger::Logger> m_logger; // Application logger system
std::unique_ptr<requirementManager::RequirementManager> m_requirementManager; // Requirement manager system
private:
void init(); // Initialize the core manager (e.g., set up the bus, etc.)
void release(); // Release resources used by the core manager (e.g., clean up the bus, etc.)
};
//--------------------------------------------------------------
} // namespace core

172
src/core/logger/logger.cpp Normal file
View File

@@ -0,0 +1,172 @@
#include "logger.h"
#include <iostream>
#include <syncstream>
using namespace std;
using namespace core::logger;
//--------------------------------------------------------------
/* Constructor */
Logger::Logger(dBus::Bus &bus)
: Node(bus)
{
// Initialize the log printing loop
runPrintingLoop();
// Initialize the bus listener thread
runBusListener();
}
//--------------------------------------------------------------
/* Default destructor */
Logger::~Logger()
{
// Stop the bus listener thread if it's running
stopBusListener();
// Stop the log printing loop if it's running
stopPrintingLoop();
}
//--------------------------------------------------------------
/* Run the loop that periodically prints log entries to the console */
void Logger::runPrintingLoop()
{
/* Start the printing thread to periodically print log entries to the console.
* The thread will continuously wait for messages and process them until a stop is requested */
// clang-format off
m_printingThread = std::jthread([this](const std::stop_token &stopToken)
{
while (!stopToken.stop_requested())
{
// Sleep for a short duration to avoid busy waiting and reduce CPU usage
this_thread::sleep_for(100ms);
// Print the log entries after processing the message
if (m_logEntries.timer.isElapsed(LOG_PRINT_INTERVAL, true))
printLogEntries();
}
});
// clang-format on
}
//--------------------------------------------------------------
/* Stop the log printing loop */
void Logger::stopPrintingLoop()
{
// Stop printing thread loop
m_printingThread.request_stop();
// Despite a jthread will automatically join in its destructor, we
// can explicitly join here to ensure that the thread has finished
// executing before the Logger object is destroyed.
// This is especially important if the thread is still running and
// may access resources that are being cleaned up in the destructor.
// By joining the thread, we can ensure a clean shutdown of the Logger
// and avoid potential issues with dangling threads or accessing invalid resources.
if (m_printingThread.joinable())
m_printingThread.join();
}
//--------------------------------------------------------------
/* Print the log entries to the console */
void Logger::printLogEntries()
{
// Move the log entries to a local variable and clear the
// original vector to minimize the time the mutex is locked
std::vector<std::string> logEntries;
{
scoped_lock lock(m_logEntries.mtx);
logEntries = std::move(m_logEntries.entries);
m_logEntries.entries.clear();
}
for (const auto &entry : logEntries)
{
// Use osyncstream to ensure thread-safe output to the console
std::osyncstream syncOut(std::cout);
syncOut << entry << std::endl;
}
}
//--------------------------------------------------------------
/* Process a log message received from the bus */
void Logger::processUserMessage(const LogMessage_ptr &logMessage)
{
// Sanity check
if (!logMessage)
return;
// Add the log entry to the vector of log entries
std::scoped_lock lock(m_logEntries.mtx);
m_logEntries.entries.push_back(logMessage->toString());
}
//--------------------------------------------------------------
/* Process a system log message received from the bus */
void Logger::processSystemMessage(const std::string &logMessage)
{
// Sanity check
if (logMessage.empty())
return;
// Add the log entry to the vector of log entries
std::scoped_lock lock(m_logEntries.mtx);
m_logEntries.entries.push_back(logMessage);
}
//--------------------------------------------------------------
/* Run the bus listener thread */
void Logger::runBusListener()
{
// Subscribe to the bus in broadcast mode to receive all messages posted to the bus
m_bus.subscribeToBroadcast(this);
/* Start the bus listener thread to process incoming messages related to requirements.
* The thread will continuously wait for messages and process them until a stop is requested */
// clang-format off
m_busListenerThread = std::jthread([this](const std::stop_token &stopToken)
{
while (!stopToken.stop_requested())
{
// Wait for a message to be received
syncWaitForMessage();
// Process all received messages
while (getMessageCount() > 0)
{
auto message = popNextMessage();
if (message)
processMessageBus(message);
}
}
});
// clang-format on
}
//--------------------------------------------------------------
/* Stop the bus listener thread */
void Logger::stopBusListener()
{
// Stop event thread loop
m_busListenerThread.request_stop();
// Wake up event thread if waiting
notifyMessageQueue();
// Join the bus listener thread to ensure it has finished
// executing before the Logger object is destroyed
if (m_busListenerThread.joinable())
m_busListenerThread.join();
}
//--------------------------------------------------------------
/* Process a message received from the bus */
void Logger::processMessageBus(const Message_ptr &message)
{
// Check the message type and process accordingly
const auto messageType = message->getMessageTypeID();
if (messageType == dBus::makeID("log.message"))
{
// Process log message
processUserMessage(message->as<dBus::EventMessage<api::log::LogMessage>>());
}
else
{
// Process system log message (for any other message type, we will log it as a system message)
processSystemMessage(message->toString());
}
}
//--------------------------------------------------------------

53
src/core/logger/logger.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <dBus/api/log.h>
#include <dBus/dBus.h>
#include <mutex>
#include <sdi_toolBox/dateTime/timer.h>
#include <thread>
namespace core::logger
{
//--------------------------------------------------------------
class Logger : public dBus::Node
{
static constexpr auto LOG_PRINT_INTERVAL = std::chrono::seconds{ 1 };
public:
Logger() = delete; // Default constructor
virtual ~Logger(); // Default destructor
Logger(const Logger &obj) = delete; // Copy constructor
Logger(Logger &&obj) noexcept = delete; // Move constructor
Logger &operator=(const Logger &obj) = delete; // Copy assignment operator
Logger &operator=(Logger &&obj) noexcept = delete; // Move assignment operator
explicit Logger(dBus::Bus &bus); // Constructor
protected:
struct
{
std::mutex mtx; // Mutex to protect access to the log entries vector
sdi_toolBox::dateTime::Timer timer; // Timer to control the frequency of log printing
std::vector<std::string> entries; // Vector to store log entries received from the bus
} m_logEntries;
private:
void runPrintingLoop(); // Run the loop that periodically prints log entries to the console
void stopPrintingLoop(); // Stop the log printing loop
void printLogEntries(); // Print the log entries to the console
std::jthread m_printingThread; // Thread for printing log entries to the console
// Logger management
using LogMessage_ptr = std::shared_ptr<dBus::EventMessage<api::log::LogMessage>>;
void processUserMessage(const LogMessage_ptr &logMessage); // Process a log message received from the bus
void processSystemMessage(const std::string &logMessage); // Process a system log message received from the bus
// dBus management
using Message_ptr = std::shared_ptr<dBus::Message>;
void runBusListener(); // Run the bus listener thread
void stopBusListener(); // Stop the bus listener thread
void processMessageBus(const Message_ptr &message); // Process a message received from the bus
std::jthread m_busListenerThread; // Thread for listening to bus messages related to requirements
};
//--------------------------------------------------------------
} // namespace core::logger

View File

@@ -0,0 +1,89 @@
#include "requirementManager.h"
using namespace std;
using namespace core::requirementManager;
//--------------------------------------------------------------
/* Constructor */
RequirementManager::RequirementManager(dBus::Bus &bus)
: Node(bus)
{
// Initialize the bus listener thread
runBusListener();
}
//--------------------------------------------------------------
/* Default destructor */
RequirementManager::~RequirementManager()
{
// Stop the bus listener thread if it's running
stopBusListener();
}
//--------------------------------------------------------------
/* Get the root requirement */
api::requirement::Requirement &RequirementManager::getRootRequirement()
{
return m_rootRequirement;
}
//--------------------------------------------------------------
/* Run the bus listener thread */
void RequirementManager::runBusListener()
{
// Subscribe to the bus for messages related to log entries
subscribe(dBus::makeID("requirement.file.operation"));
/* Start the bus listener thread to process incoming messages related to requirements.
* The thread will continuously wait for messages and process them until a stop is requested. */
// clang-format off
m_busListenerThread = std::jthread([this](const std::stop_token &stopToken)
{
while (!stopToken.stop_requested())
{
// Wait for a message to be received
syncWaitForMessage();
// Process all received messages
while (getMessageCount() > 0)
{
auto message = popNextMessage();
if (message)
processMessageBus(message);
}
}
});
// clang-format on
}
//--------------------------------------------------------------
/* Stop the bus listener thread */
void RequirementManager::stopBusListener()
{
// Stop event thread loop
m_busListenerThread.request_stop();
// Wake up event thread if waiting
notifyMessageQueue();
/* No need to join the thread explicitly as std::jthread will
* automatically join in its destructor.
* Subscribe to the bus will be automatically cleaned up in the
* Node destructor. */
}
//--------------------------------------------------------------
/* Process a message received from the bus */
void RequirementManager::processMessageBus(const Message_ptr &message)
{
const auto messageType = message->getMessageTypeID();
switch (messageType)
{
case dBus::makeID("requirement.file.operation"):
{
// Process file operation request
const auto requestMessage = message->as<dBus::RequestMessage<api::requirement::FileOperationEvent, void>>();
if (!requestMessage)
break;
requestMessage->responsePromise.set_exception(std::make_exception_ptr(std::runtime_error("Failed to process file operation request")));
break;
}
}
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,38 @@
#pragma once
#include <api/requirement.h>
#include <dBus/dBus.h>
#include <thread>
namespace core::requirementManager
{
//--------------------------------------------------------------
class RequirementManager : public dBus::Node
{
public:
RequirementManager() = delete; // Default constructor
virtual ~RequirementManager(); // Default destructor
RequirementManager(const RequirementManager &obj) = delete; // Copy constructor
RequirementManager(RequirementManager &&obj) noexcept = delete; // Move constructor
RequirementManager &operator=(const RequirementManager &obj) = delete; // Copy assignment operator
RequirementManager &operator=(RequirementManager &&obj) noexcept = delete; // Move assignment operator
explicit RequirementManager(dBus::Bus &bus); // Constructor
protected:
// dBus::Bus &m_bus; // Reference to the application bus
api::requirement::Requirement m_rootRequirement;
private:
api::requirement::Requirement &getRootRequirement(); // Get the root requirement
// dBus management
using Message_ptr = std::shared_ptr<dBus::Message>;
void runBusListener(); // Run the bus listener thread
void stopBusListener(); // Stop the bus listener thread
void processMessageBus(const Message_ptr &message); // Process a message received from the bus
std::jthread m_busListenerThread; // Thread for listening to bus messages related to requirements
};
//--------------------------------------------------------------
} // namespace core::requirementManager

219
src/dBus/api/api.h Normal file
View File

@@ -0,0 +1,219 @@
#pragma once
#include "logWrapper.h"
#include <chrono>
#include <dBus/dBus.h>
#include <dBus/defs.h>
#include <expected>
#include <format>
#include <future>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
namespace dBus::api
{
// Define a type alias for the return status of post
// operations, using std::expected to represent success
// or failure with an error code
using PostReturnStatus = std::expected<void, dBus::ReturnStatus>;
//--------------------------------------------------------------
struct BaseDefaultData
{
};
template<HashID ID>
struct DefaultData : BaseDefaultData
{
DefaultData() = default; // Default constructor
virtual ~DefaultData() = default; // Default destructor
DefaultData(const DefaultData &obj) = default; // Copy constructor
DefaultData(DefaultData &&obj) noexcept = default; // Move constructor
DefaultData &operator=(const DefaultData &obj) = default; // Copy assignment operator
DefaultData &operator=(DefaultData &&obj) noexcept = default; // Move assignment operator
constexpr HashID getID() const
{
return ID;
}
[[nodiscard]] virtual std::string toString() const = 0; // Convert the data to a string representation for logging or debugging
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
// Generic message API template for simple data transfer (fire-and-forget)
template<class T>
class MessageApi : public EventMessage<T>
{
public:
MessageApi() = delete; // Default constructor
virtual ~MessageApi() = default; // Default destructor
MessageApi(const MessageApi &obj) = delete; // Copy constructor
MessageApi(MessageApi &&obj) noexcept = delete; // Move constructor
MessageApi &operator=(const MessageApi &obj) = delete; // Copy assignment operator
MessageApi &operator=(MessageApi &&obj) noexcept = delete; // Move assignment operator
explicit MessageApi(const HashID &messageTypeID, T data, const MessageCategory &category = MessageCategory::System) // Constructor
: EventMessage<T>(messageTypeID, category, std::move(data))
{
}
// Serialize the message data to a string for logging or debugging
[[nodiscard]] std::string serializeData() const override
{
if constexpr (std::is_base_of_v<BaseDefaultData, T>)
{
return this->value.toString();
}
else if constexpr (std::is_arithmetic_v<T>)
{
return std::to_string(this->value);
}
else
{
return "";
}
}
};
//--------------------------------------------------------------
// Request message API template for data transfer with response
template<class TRequest, class TResponse>
class RequestMessageApi : public RequestMessage<TRequest, TResponse>
{
public:
RequestMessageApi() = delete; // Default constructor
virtual ~RequestMessageApi() = default; // Default destructor
RequestMessageApi(const RequestMessageApi &obj) = delete; // Copy constructor
RequestMessageApi(RequestMessageApi &&obj) noexcept = delete; // Move constructor
RequestMessageApi &operator=(const RequestMessageApi &obj) = delete; // Copy assignment operator
RequestMessageApi &operator=(RequestMessageApi &&obj) noexcept = delete; // Move assignment operator
explicit RequestMessageApi(const HashID &messageTypeID, TRequest request, const MessageCategory &category = MessageCategory::System)
: RequestMessage<TRequest, TResponse>(messageTypeID, category, std::move(request), createResponse())
{
}
// Serialize the message data to a string for logging or debugging
[[nodiscard]] std::string serializeData() const override
{
if constexpr (std::is_base_of_v<BaseDefaultData, TRequest>)
{
return this->value.toString();
}
else if constexpr (std::is_arithmetic_v<TRequest>)
{
return std::to_string(this->value);
}
else
{
return "";
}
}
private:
static auto createResponse()
{
if constexpr (std::is_void_v<TResponse>)
return nullptr; // Use nullptr for void response type since there is no value to return
else
return std::make_shared<TResponse>();
}
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
// Post a simple data message (fire-and-forget)
template<class T>
bool postData(Bus &bus,
const HashID &messageTypeID,
T data,
const MessageCategory &category)
{
auto msg = std::make_shared<MessageApi<T>>(messageTypeID, std::move(data), category);
return bus.post(msg);
}
//--------------------------------------------------------------
// Post a data message with subscriber check
template<class T>
std::expected<void, ReturnStatus> postDataChecked(Bus &bus,
const HashID &messageTypeID,
T data,
const MessageCategory &category)
{
auto msg = std::make_shared<MessageApi<T>>(messageTypeID, std::move(data), category);
const auto subscribersFound = bus.post(msg);
if (!subscribersFound)
{
return std::unexpected(ReturnStatus::NoSubscribers);
}
return {};
}
//--------------------------------------------------------------
template<class TRequest, class TResponse>
std::expected<TResponse, ReturnStatus> requestData(Bus &bus,
const HashID &messageTypeID,
TRequest request,
const MessageCategory &category,
const std::chrono::milliseconds timeout = DEFAULT_TIMEOUT)
{
auto msg = std::make_shared<RequestMessageApi<TRequest, TResponse>>(messageTypeID, std::move(request), category);
auto future = msg->getFuture();
try
{
// Post the message to the bus and check if there are subscribers
const auto subscribersFound = bus.post(msg);
if (!subscribersFound)
{
::api::log::postLogWarning(bus, std::format("No subscribers found for message type: 0x{:08x}", msg->getMessageTypeID()));
return std::unexpected(ReturnStatus::NoSubscribers);
}
const auto status = future.wait_for(timeout);
if (status == std::future_status::ready)
{
if constexpr (std::is_void_v<TResponse>)
{
// Wait for the future to be ready and check for exceptions,
// but ignore the value since it's void
future.get();
return {}; // Return success without a value
}
else
{
// Wait for the future to be ready and return the value,
// or propagate any exceptions that occurred during the
// request handling
return future.get(); // Return success with value
}
}
else if (status == std::future_status::timeout)
{
::api::log::postLogWarning(bus, std::format("Timeout while waiting for response to message type: 0x{:08x}", msg->getMessageTypeID()));
return std::unexpected(ReturnStatus::Timeout);
}
else
{
::api::log::postLogError(bus, std::format("Unexpected future status while waiting for response to message type: 0x{:08x}", msg->getMessageTypeID()));
return std::unexpected(ReturnStatus::Error);
}
}
catch (const std::exception &e)
{
::api::log::postLogError(bus, std::format("Exception in requestData for message type: 0x{:08x}: {}", msg->getMessageTypeID(), e.what()));
return std::unexpected(ReturnStatus::Exception);
}
}
//--------------------------------------------------------------
} // namespace dBus::api

70
src/dBus/api/log.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include "api.h"
#include <sdi_toolBox/dateTime/age.h>
namespace api::log
{
//--------------------------------------------------------------
// Log levels
//--------------------------------------------------------------
enum class LogLevel : uint8_t
{
Message = 0,
Debug,
Info,
Warning,
Error,
System, // Reserved for system-level logs, not intended for user messages
};
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
// Log message structure
//--------------------------------------------------------------
struct LogMessage : dBus::api::DefaultData<dBus::makeID("log.message")>
{
using Timestamp = std::chrono::steady_clock::time_point;
virtual ~LogMessage() = default;
Timestamp timestamp = std::chrono::steady_clock::now();
LogLevel level = LogLevel::Message;
std::string message;
[[nodiscard]] std::string toString() const override
{
const auto timestampFormatter = Age(std::chrono::duration_cast<std::chrono::nanoseconds>(timestamp.time_since_epoch()));
const auto logEntry = std::format("{}{}",
getLevelString(),
message);
return logEntry;
}
private:
[[nodiscard]] std::string getLevelString() const
{
switch (level)
{
using enum LogLevel;
case Debug:
return "[Debug] ";
case Info:
return "[Info] ";
case Warning:
return "[Warning] ";
case Error:
return "[Error] ";
case System:
return "[System] ";
default:
return "";
}
}
};
//--------------------------------------------------------------
} // namespace api::log

View File

@@ -0,0 +1,50 @@
#include "logWrapper.h"
#include "log.h"
namespace
{
//--------------------------------------------------------------
void postMessage(dBus::Bus &bus, const api::log::LogLevel logLevel, const std::string &message)
{
api::log::LogMessage logMsg;
logMsg.level = logLevel;
logMsg.message = message;
// Post the log message to the bus with the appropriate category (Fire-and-forget, no response expected)
(void)dBus::api::postData(bus, logMsg.getID(), logMsg, dBus::MessageCategory::Log);
}
//--------------------------------------------------------------
} // namespace
//--------------------------------------------------------------
void api::log::postLogMessage(dBus::Bus &bus, const std::string &message)
{
postMessage(bus, LogLevel::Message, message);
}
//--------------------------------------------------------------
void api::log::postLogDebug(dBus::Bus &bus, const std::string &message)
{
postMessage(bus, LogLevel::Debug, message);
}
//--------------------------------------------------------------
void api::log::postLogInfo(dBus::Bus &bus, const std::string &message)
{
postMessage(bus, LogLevel::Info, message);
}
//--------------------------------------------------------------
void api::log::postLogWarning(dBus::Bus &bus, const std::string &message)
{
postMessage(bus, LogLevel::Warning, message);
}
//--------------------------------------------------------------
void api::log::postLogError(dBus::Bus &bus, const std::string &message)
{
postMessage(bus, LogLevel::Error, message);
}
//--------------------------------------------------------------
void api::log::postLogSystem(dBus::Bus &bus, const std::string &message)
{
postMessage(bus, LogLevel::System, message);
}
//--------------------------------------------------------------

24
src/dBus/api/logWrapper.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include "../bus.h"
#include <string>
namespace api::log
{
//--------------------------------------------------------------
// Post event helpers
//--------------------------------------------------------------
void postLogMessage(dBus::Bus &bus, const std::string &message);
//--------------------------------------------------------------
void postLogDebug(dBus::Bus &bus, const std::string &message);
//--------------------------------------------------------------
void postLogInfo(dBus::Bus &bus, const std::string &message);
//--------------------------------------------------------------
void postLogWarning(dBus::Bus &bus, const std::string &message);
//--------------------------------------------------------------
void postLogError(dBus::Bus &bus, const std::string &message);
//--------------------------------------------------------------
void postLogSystem(dBus::Bus &bus, const std::string &message);
//--------------------------------------------------------------
} // namespace api::log

274
src/dBus/bus.cpp Normal file
View File

@@ -0,0 +1,274 @@
#include "bus.h"
#include "dbus.h"
#include <ranges>
#include <set>
using namespace std;
using namespace dBus;
//--------------------------------------------------------------
/* Default constructor */
Bus::Bus()
{
// Initialization
m_startTimestamp = std::chrono::steady_clock::now();
// Start the monitoring thread
m_monitoringThread = jthread([&](const stop_token &st)
{
while (!st.stop_requested())
{
this_thread::sleep_for(100ms);
updateMonitoringData();
} });
}
//--------------------------------------------------------------
/* Get the timestamp of when the bus started */
TimePoint Bus::getStartTimestamp() const
{
return m_startTimestamp;
}
//--------------------------------------------------------------
/* Subscribe a listener to a specific event type */
void Bus::subscribe(const HashID &eventType, Node *node)
{
// Sanity check
if (!node)
throw runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Event type not registered yet, create a new entry with the node
if (!m_routingTable.nodes.contains(eventType))
{
m_routingTable.nodes[eventType] = { node };
m_routingTable.activeNodesCache.insert(node);
return;
}
// Event type already registered, add the node if not already subscribed
auto &nodes = m_routingTable.nodes.at(eventType);
if (std::ranges::find(nodes, node) == nodes.end())
{
nodes.push_back(node);
m_routingTable.activeNodesCache.insert(node);
}
}
//--------------------------------------------------------------
/* Unsubscribe a listener from a specific event type */
void Bus::unsubscribe(const HashID &eventType, Node *node)
{
// Sanity check
if (!node)
throw runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Event type not recorded, nothing to do
if (!m_routingTable.nodes.contains(eventType))
return;
// Event type registered, remove the listener if subscribed
auto &nodes = m_routingTable.nodes.at(eventType);
std::erase(nodes, node);
// Check if the node is still subscribed to any message type before removing it from the active nodes cache
if (!isSubscribedEverywhere(node))
m_routingTable.activeNodesCache.erase(node);
}
//--------------------------------------------------------------
/* Unsubscribe a listener from all event types */
void Bus::unsubscribeAll(Node *node)
{
// Sanity check
if (!node)
throw 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.nodes | std::views::values)
{
std::erase(nodeList, node);
}
// Check if the node is still subscribed to any message type before removing it from the active nodes cache
if (!isSubscribedEverywhere(node))
m_routingTable.activeNodesCache.erase(node);
}
//--------------------------------------------------------------
/* Check if a listener is subscribed to a specific event type */
bool Bus::isSubscribed(const HashID &eventType, const Node *node) const
{
// Sanity check
if (!node)
throw runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Event type not recorded, node not subscribed
if (!m_routingTable.nodes.contains(eventType))
return false;
// Event type recorded, check if the node is in the list
const auto &nodes = m_routingTable.nodes.at(eventType);
return std::ranges::find(nodes, node) != nodes.end();
}
//--------------------------------------------------------------
/* Subscribe a node to receive all messages (broadcast mode) */
void Bus::subscribeToBroadcast(Node *node)
{
// Sanity check
if (!node)
throw runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Add the node to the broadcast nodes set
m_routingTable.broadcastNodes.insert(node);
m_routingTable.activeNodesCache.insert(node);
}
//--------------------------------------------------------------
/* Unsubscribe a node from broadcast mode */
void Bus::unsubscribeFromBroadcast(Node *node)
{
// Sanity check
if (!node)
throw runtime_error("invalid node handle");
std::scoped_lock lock(m_routingTable.mtx);
// Remove the node from the broadcast nodes set
m_routingTable.broadcastNodes.erase(node);
// Check if the node is still subscribed to any message type before removing it from the active nodes cache
if (!isSubscribedEverywhere(node))
m_routingTable.activeNodesCache.erase(node);
}
//--------------------------------------------------------------
/* Check if a node is subscribed to broadcast mode */
bool Bus::isSubscribedToBroadcast(Node *node) const
{
// Sanity check
if (!node)
throw 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.broadcastNodes.contains(node);
}
//--------------------------------------------------------------
bool Bus::post(const std::shared_ptr<Message> &message)
{
// Link the message to the bus (set the posted information for the message)
message->linkToBus(this);
// Record the incoming message for monitoring purposes
recordIncoming();
std::scoped_lock lock(m_routingTable.mtx);
// Get the list of nodes subscribed to the message type
set<Node *> recipients;
// Add nodes subscribed to the specific message type
if (m_routingTable.nodes.contains(message->getMessageTypeID()))
{
const auto &nodeList = m_routingTable.nodes.at(message->getMessageTypeID());
for (const auto &node : nodeList)
recipients.insert(node);
}
const bool subscribersFound = !recipients.empty();
// Add broadcast nodes
recipients.insert(m_routingTable.broadcastNodes.begin(),
m_routingTable.broadcastNodes.end());
// Dispatch the message to all recipients
for (const auto &node : recipients)
node->receiveMessageFromBus(message);
return subscribersFound;
}
//--------------------------------------------------------------
/* Get the current monitoring data of the bus */
Bus::MonitoringData Bus::getMonitoringData() const
{
std::scoped_lock lock(m_monitoring.mtx);
return m_monitoring.data;
}
//--------------------------------------------------------------
/* Set the age of the message (to be called by the message when it is destroyed) */
void Bus::setMessageAge(std::chrono::microseconds age)
{
scoped_lock lock(m_monitoring.mtx);
m_monitoring.data.maxAge = std::max(m_monitoring.data.maxAge, age);
}
//--------------------------------------------------------------
/* Check if a node is subscribed to any message type (internal, assumes lock is held) */
bool Bus::isSubscribedEverywhere(Node *node) const
{
// Sanity check
if (!node)
throw runtime_error("invalid node handle");
// Check if the node is still subscribed to any message type
for (const auto &nodeList : m_routingTable.nodes | std::views::values)
{
if (std::ranges::find(nodeList, node) != nodeList.end())
return true;
}
// Check if the node is subscribed to broadcast mode
if (m_routingTable.broadcastNodes.contains(node))
return true;
return false;
}
//--------------------------------------------------------------
/* Incoming messages for monitoring purposes */
void Bus::recordIncoming()
{
m_monitoring.messageCounter++;
}
//--------------------------------------------------------------
/* Update the monitoring data of the bus */
void Bus::updateMonitoringData()
{
if (!m_monitoring.frequencyTimer.isElapsed(10s))
return;
// Update monitoring data
MonitoringData monitoringData;
{
std::scoped_lock lock(m_routingTable.mtx, m_monitoring.mtx);
// Update the global message count with the number of messages received in the last interval
m_monitoring.data.globalMessageCount += m_monitoring.messageCounter;
// Calculate the frequency of messages being posted to the bus (messages per second)
const auto duration_s = static_cast<double>(chrono::duration_cast<chrono::milliseconds>(m_monitoring.frequencyTimer.getElapsed()).count()) / 1000.0;
m_monitoring.data.frequencyHz = static_cast<double>(m_monitoring.messageCounter) / duration_s;
// Update the current message count and maximum age of messages on the bus
m_monitoring.data.currentMessageCount = 0;
for (const auto &node : m_routingTable.activeNodesCache)
m_monitoring.data.currentMessageCount += node->getMessageCount();
// Copy the monitoring data to a local variable for posting the monitoring message after releasing the locks
monitoringData = m_monitoring.data;
// Reset all monitoring counters and timers for the next interval
m_monitoring.data.maxAge = {};
m_monitoring.messageCounter = 0;
m_monitoring.frequencyTimer.reset();
}
// Post monitoring message to the bus
// TODO
//postMonitoringMsg(*this, monitoringData);
}
//--------------------------------------------------------------

83
src/dBus/bus.h Normal file
View File

@@ -0,0 +1,83 @@
#pragma once
#include "Message.h"
#include "Node.h"
#include "defs.h"
#include <expected>
#include <mutex>
#include <sdi_toolBox/dateTime/timer.h>
#include <set>
#include <unordered_map>
namespace dBus
{
//--------------------------------------------------------------
class Bus
{
public:
struct MonitoringData
{
size_t globalMessageCount{ 0 }; // Total count of messages posted to the bus
double frequencyHz = { 0 }; // Current frequency of messages being posted to the bus (calculated as messages per second)
size_t currentMessageCount{ 0 }; // Current count of messages on the bus
std::chrono::microseconds maxAge{}; // Maximum age of messages on the bus
};
public:
Bus(); // Default constructor
virtual ~Bus() = default; // Default destructor
Bus(const Bus &obj) = delete; // Copy constructor
Bus(Bus &&obj) noexcept = delete; // Move constructor
Bus &operator=(const Bus &obj) = delete; // Copy assignment operator
Bus &operator=(Bus &&obj) noexcept = delete; // Move assignment operator
[[nodiscard]] TimePoint getStartTimestamp() const; // Get the timestamp of when the bus started
// Registration and unregistration of listeners
void subscribe(const HashID &eventType, Node *node); // Subscribe a listener to a specific event type
void unsubscribe(const HashID &eventType, Node *node); // Unsubscribe a listener from a specific event type
void unsubscribeAll(Node *node); // Unsubscribe a listener from all event types
[[nodiscard]] bool isSubscribed(const HashID &eventType, const Node *node) const; // Check if a listener is subscribed to a specific event type
// Broadcast management
void subscribeToBroadcast(Node *node); // Subscribe a node to receive all messages (broadcast mode)
void unsubscribeFromBroadcast(Node *node); // Unsubscribe a node from broadcast mode
[[nodiscard]] bool isSubscribedToBroadcast(Node *node) const; // Check if a node is subscribed to broadcast mode
// Data transmission
bool post(const std::shared_ptr<Message> &message); // Post a message to the bus
// Monitoring
MonitoringData getMonitoringData() const; // Get the current monitoring data of the bus (message count, frequency, etc.)
void setMessageAge(std::chrono::microseconds age); // Set the age of the message
protected:
TimePoint m_startTimestamp; // Timestamp of when the bus started
std::jthread m_monitoringThread; // Thread for monitoring the performance of the bus
struct
{
mutable std::mutex mtx; // Mutex for thread-safe access to the nodes map
std::unordered_map<HashID, std::vector<Node *>> nodes; // Map of message type IDs to lists of subscribed nodes
std::set<Node *> broadcastNodes; // Set of nodes subscribed to receive all messages (broadcast mode)
std::set<Node *> activeNodesCache; // Cache of active nodes for quick access during message routing
} m_routingTable;
struct
{
mutable std::mutex mtx; // Mutex for thread-safe access to the monitoring data
MonitoringData data;
sdi_toolBox::dateTime::Timer frequencyTimer; // Timer for calculating message frequency
size_t messageCounter = { 0 }; // Counter for calculating message frequency
} m_monitoring;
private:
bool isSubscribedEverywhere(Node *node) const; // Check if a node is subscribed to any message type (used for managing the active nodes cache)
void recordIncoming(); // Incoming messages for monitoring purposes
void updateMonitoringData(); // Update the monitoring data of the bus (message count, frequency, etc.)
};
//--------------------------------------------------------------
} // namespace dBus

8
src/dBus/dBus.h Normal file
View File

@@ -0,0 +1,8 @@
#pragma once
//--------------------------------------------------------------
#include "bus.h"
#include "defs.h"
#include "message.h"
#include "node.h"
//--------------------------------------------------------------

105
src/dBus/defs.h Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
#include <chrono>
#include <cstdint>
#include <format>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
namespace dBus
{
using HashID = uint32_t;
using TimePoint = std::chrono::steady_clock::time_point;
constexpr auto DEFAULT_TIMEOUT = std::chrono::milliseconds(100);
//--------------------------------------------------------------
// Hash function for IDs using FNV-1a algorithm
constexpr HashID makeID(const std::string_view id) noexcept
{
constexpr uint32_t FNV_offset_basis = 2166136261u;
constexpr uint32_t FNV_prime = 16777619u;
uint32_t hash_value = FNV_offset_basis;
for (const char c : id)
{
hash_value ^= static_cast<uint32_t>(static_cast<uint8_t>(c));
hash_value *= FNV_prime;
}
return hash_value;
}
//--------------------------------------------------------------
template<typename T, typename = void>
struct has_to_string : std::false_type
{
};
template<typename T>
struct has_to_string<T, std::void_t<decltype(std::to_string(std::declval<T>()))>>
: std::true_type
{
};
template<typename T>
constexpr bool has_to_string_v = has_to_string<T>::value;
//--------------------------------------------------------------
enum class ReturnStatus : uint8_t
{
Success = 0,
Timeout,
NoSubscribers,
Error,
Exception,
};
//--------------------------------------------------------------
enum class MessageCategory : uint32_t
{
None = 0,
Log = 1 << 0,
Hardware = 1 << 1,
Dataset = 1 << 2,
Database = 1 << 3,
System = 1 << 4,
UI = 1 << 5,
All = 0xFFFFFFFF
};
// Convert LogCategory to a string representation
inline std::string logCategoryToString(const MessageCategory &cat)
{
static const std::unordered_map<MessageCategory, std::string> names = {
{ MessageCategory::None, "None" },
{ MessageCategory::Log, "Log" },
{ MessageCategory::Hardware, "Hardware" },
{ MessageCategory::Dataset, "Dataset" },
{ MessageCategory::Database, "Database" },
{ MessageCategory::System, "System" },
{ MessageCategory::UI, "UI" },
{ MessageCategory::All, "All" }
};
const auto it = names.find(cat);
return (it != names.end()) ? it->second : "Composite/Unknown";
}
// Bitwise OR operator for LogCategory to allow combining categories
inline MessageCategory operator|(const MessageCategory lhs, const MessageCategory rhs)
{
return static_cast<MessageCategory>(
static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
}
// Compound assignment operator for LogCategory
inline MessageCategory &operator|=(MessageCategory &lhs, const MessageCategory rhs)
{
lhs = lhs | rhs;
return lhs;
}
// Bitwise AND operator for LogCategory to check if a category is included in a combination
inline bool operator&(const MessageCategory lhs, const MessageCategory rhs)
{
return (static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs)) != 0;
}
//--------------------------------------------------------------
} // namespace dBus

125
src/dBus/message.cpp Normal file
View File

@@ -0,0 +1,125 @@
#include "Message.h"
#include "bus.h"
#include "defs.h"
#include <sdi_toolBox/dateTime/age.h>
using namespace std;
using namespace dBus;
//--------------------------------------------------------------
/* Constructor */
Message::Message(const HashID &messageTypeID, const MessageCategory &category)
{
// Default initialization
m_postedAt = std::chrono::steady_clock::now();
// Set the message parameters
m_messageTypeID = messageTypeID;
m_category = category;
}
//--------------------------------------------------------------
/* Default destructor */
Message::~Message()
{
// Update the age of the message (to be used when logging the message in the bus)
if (m_bus)
{
const auto finalAge = getAge();
m_bus->setMessageAge(finalAge);
}
}
//--------------------------------------------------------------
/* Get the timestamp of when the message was posted */
TimePoint Message::getPostedAt() const
{
return m_postedAt;
}
//--------------------------------------------------------------
/* Set the message parameters when the message is posted to the bus (to be called by the bus when the message is posted) */
void Message::linkToBus(Bus *bus)
{
m_bus = bus;
}
//--------------------------------------------------------------
/* Get the age of the message in microseconds */
std::chrono::microseconds Message::getAge() const
{
const auto now = std::chrono::steady_clock::now();
const auto age = now - m_postedAt;
return std::chrono::duration_cast<std::chrono::microseconds>(age);
}
//--------------------------------------------------------------
/* Check if the message is of a specific type */
bool Message::isType(const HashID &eventType) const
{
return m_messageTypeID == eventType;
}
//--------------------------------------------------------------
/* Get the unique identifier for the message type */
HashID Message::getMessageTypeID() const
{
return m_messageTypeID;
}
//--------------------------------------------------------------
/* Get the log category of the message */
MessageCategory Message::getCategory() const
{
return m_category;
}
//--------------------------------------------------------------
/* Serialize the message data to a string */
std::string Message::serializeData() const
{
return "";
}
//--------------------------------------------------------------
/* Get a string representation of the message for logging purposes */
std::string Message::toString() const
{
const auto serializedData = serializeData();
// Determine the age of the message for logging purposes
string age = "N/A";
if (m_bus)
{
const auto &busStartTimestamp = m_bus->getStartTimestamp();
const auto dt = m_postedAt - busStartTimestamp;
age = Age(dt).toString();
}
// Format message output
auto str = std::format("[{:}] - 0x{:08x} ({:})",
age,
m_messageTypeID,
logCategoryToString(m_category));
if (!serializedData.empty())
str += " - " + serializedData;
return str;
}
//--------------------------------------------------------------
/* Get a formatted log line representation of the message for logging purposes */
std::string Message::toLogLine() const
{
const auto serializedData = serializeData();
// Determine the age of the message for logging purposes
std::chrono::microseconds age{ 0 };
if (m_bus)
{
const auto &busStartTimestamp = m_bus->getStartTimestamp();
const auto dt = m_postedAt - busStartTimestamp;
age = Age(dt).getMicroseconds();
}
// Format message output
auto str = std::format("[{:}] - 0x{:08x} ({:})",
age.count(),
m_messageTypeID,
logCategoryToString(m_category));
if (!serializedData.empty())
str += " - " + serializedData;
return str;
}
//--------------------------------------------------------------

152
src/dBus/message.h Normal file
View File

@@ -0,0 +1,152 @@
#pragma once
#include "defs.h"
#include <future>
namespace dBus
{
//--------------------------------------------------------------
class Message : public std::enable_shared_from_this<Message>
{
friend class Bus;
public:
Message() = default; // Default constructor
virtual ~Message(); // Default destructor
Message(const Message &obj) = delete; // Copy constructor
Message(Message &&obj) noexcept = delete; // Move constructor
Message &operator=(const Message &obj) = delete; // Copy assignment operator
Message &operator=(Message &&obj) noexcept = delete; // Move assignment operator
explicit Message(const HashID &messageTypeID, // Constructor
const MessageCategory &category);
// Message management
TimePoint getPostedAt() const; // Get the timestamp of when the message was posted
[[nodiscard]] std::chrono::microseconds getAge() const; // Get the age of the message in microseconds
[[nodiscard]] bool isType(const HashID &eventType) const; // Check if the message is of a specific type
// Trace information
[[nodiscard]] HashID getMessageTypeID() const; // Get the unique identifier for the message type
[[nodiscard]] MessageCategory getCategory() const; // Get the log category of the message
[[nodiscard]] virtual std::string serializeData() const; // Serialize the message data to a string
[[nodiscard]] virtual std::string toString() const; // Get a string representation of the message for logging purposes
[[nodiscard]] virtual std::string toLogLine() const; // Get a formatted log line representation of the message for logging purposes
template<class Derived>
[[nodiscard]] std::shared_ptr<Derived> as(); // Template method to cast the message to a specific derived type
template<class Derived>
[[nodiscard]] std::shared_ptr<const Derived> as() const; // Template method to cast the message to a specific derived type
protected:
HashID m_messageTypeID = 0; // Unique identifier for the message type
MessageCategory m_category = MessageCategory::None; // Log category of the message
Bus *m_bus = nullptr; // Pointer to the bus this message is posted to (to be set by the bus when the message is posted)
TimePoint m_postedAt; // Timestamp of when the message was posted
private:
void linkToBus(Bus *bus); // Set the message parameters when the message is posted to the bus
};
//--------------------------------------------------------------
/* Template method to cast the message to a specific derived type */
template<class Derived>
std::shared_ptr<Derived> Message::as()
{
return std::dynamic_pointer_cast<Derived>(shared_from_this());
}
//--------------------------------------------------------------
/* Template method to cast the message to a specific derived type */
template<class Derived>
std::shared_ptr<const Derived> Message::as() const
{
return std::dynamic_pointer_cast<const Derived>(shared_from_this());
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
template<class T>
class EventMessage : public Message
{
public:
T value; // Value associated with the event message
EventMessage() = delete; // Default constructor
virtual ~EventMessage() = default; // Default destructor
EventMessage(const EventMessage &obj) = delete; // Copy constructor
EventMessage(EventMessage &&obj) noexcept = delete; // Move constructor
EventMessage &operator=(const EventMessage &obj) = delete; // Copy assignment operator
EventMessage &operator=(EventMessage &&obj) noexcept = delete; // Move assignment operator
explicit EventMessage(const HashID &messageTypeID, // Constructor
const MessageCategory &category,
T v);
[[nodiscard]] std::string serializeData() const override; // Serialize the message data to a string
};
//--------------------------------------------------------------
/* Constructor */
template<class T>
EventMessage<T>::EventMessage(const HashID &messageTypeID, const MessageCategory &category, T v)
: Message(messageTypeID, category)
, value(std::move(v))
{
}
//--------------------------------------------------------------
/* Serialize the message data to a string */
template<class T>
std::string EventMessage<T>::serializeData() const
{
if constexpr (std::is_arithmetic_v<T>)
return std::to_string(value);
return "";
}
//--------------------------------------------------------------
/* --- */
//--------------------------------------------------------------
template<class T, class U>
class RequestMessage : public Message
{
public:
T value; // Value associated with the event message
std::promise<U> responsePromise; // Promise to be fulfilled with the response data once the request is processed
RequestMessage() = delete; // Default constructor
virtual ~RequestMessage() = default; // Default destructor
RequestMessage(const RequestMessage &obj) = delete; // Copy constructor
RequestMessage(RequestMessage &&obj) noexcept = delete; // Move constructor
RequestMessage &operator=(const RequestMessage &obj) = delete; // Copy assignment operator
RequestMessage &operator=(RequestMessage &&obj) noexcept = delete; // Move assignment operator
explicit RequestMessage(const HashID &messageTypeID, // Constructor
const MessageCategory &category,
T v,
std::shared_ptr<U> p);
std::future<U> getFuture(); // Get the future associated with the response promise
};
//--------------------------------------------------------------
/* Constructor */
template<class T, class U>
RequestMessage<T, U>::RequestMessage(const HashID &messageTypeID, const MessageCategory &category, T v, std::shared_ptr<U> p)
: Message(messageTypeID, category)
, value(std::move(v))
, responsePromise(std::promise<U>{})
{
}
//--------------------------------------------------------------
/* Get the future associated with the response promise */
template<class T, class U>
std::future<U> RequestMessage<T, U>::getFuture()
{
return std::move(responsePromise.get_future());
}
//--------------------------------------------------------------
} // namespace dBus

121
src/dBus/node.cpp Normal file
View File

@@ -0,0 +1,121 @@
#include "node.h"
#include "bus.h"
using namespace std;
using namespace dBus;
//--------------------------------------------------------------
/* Constructor */
Node::Node(Bus &bus)
: m_bus(bus)
{
}
//--------------------------------------------------------------
/* Default destructor */
Node::~Node()
{
unsubscribeAll();
// Notify the node thread to stop waiting for messages and exit the loop (if applicable)
notifyMessageQueue();
}
//--------------------------------------------------------------
/* Subscribe to a specific message type */
void Node::subscribe(const HashID &eventType)
{
m_bus.subscribe(eventType, this);
}
//--------------------------------------------------------------
/* Unsubscribe from a specific message type */
void Node::unsubscribe(const HashID &eventType)
{
m_bus.unsubscribe(eventType, this);
}
//--------------------------------------------------------------
/* Unsubscribe from all message types */
void Node::unsubscribeAll()
{
m_bus.unsubscribeAll(this);
}
//--------------------------------------------------------------
/* Check if is subscribed to a specific message type */
bool Node::isSubscribed(const HashID &eventType) const
{
return m_bus.isSubscribed(eventType, this);
}
//--------------------------------------------------------------
/* Wait for a message to be received */
void Node::syncWaitForMessage()
{
// Check if there are already messages in the queue before waiting
// to avoid missing notifications if a message is received between
// the last check and the wait call
{
std::scoped_lock lock(m_dBusMessages.mtx);
if (!m_dBusMessages.messageQueue.empty())
return; // Messages already in the queue, no need to wait
}
// Wait until a message is received (the bus will call notifyMessageFromBus()
// to wake up the node thread when a message is posted)
m_nodeWaitFlag = false;
m_nodeWaitFlag.wait(false);
}
//--------------------------------------------------------------
/* Post a message to the bus */
void Node::postMessage(const std::shared_ptr<Message> &message) const
{
m_bus.post(message);
}
//--------------------------------------------------------------
/* Get the maximum age of messages in the queue */
std::chrono::microseconds Node::getMessageMaxAge() const
{
std::scoped_lock lock(m_dBusMessages.mtx);
// If the queue is empty, return a default value (e.g., zero)
if (m_dBusMessages.messageQueue.empty())
return {};
// Get the age of the oldest message in the queue
// We can assume that the messages are ordered by age, so we can check the front of the queue
const auto &oldestMessage = m_dBusMessages.messageQueue.front();
return oldestMessage->getAge();
}
//--------------------------------------------------------------
/* Get the number of received messages */
size_t Node::getMessageCount() const
{
std::scoped_lock lock(m_dBusMessages.mtx);
return m_dBusMessages.messageQueue.size();
}
//--------------------------------------------------------------
/* Get the next message from the queue */
std::shared_ptr<Message> Node::popNextMessage()
{
std::scoped_lock lock(m_dBusMessages.mtx);
if (m_dBusMessages.messageQueue.empty())
return nullptr;
auto event = m_dBusMessages.messageQueue.front();
m_dBusMessages.messageQueue.pop();
return event;
}
//--------------------------------------------------------------
/* Receive a message from the bus */
void Node::receiveMessageFromBus(std::shared_ptr<Message> message)
{
std::scoped_lock lock(m_dBusMessages.mtx);
m_dBusMessages.messageQueue.push(std::move(message));
notifyMessageQueue();
}
//--------------------------------------------------------------
/* Notify the node of a new message from the bus */
void Node::notifyMessageQueue()
{
m_nodeWaitFlag.store(true);
m_nodeWaitFlag.notify_one();
}
//--------------------------------------------------------------

57
src/dBus/node.h Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include "Message.h"
#include "defs.h"
#include <mutex>
#include <queue>
namespace dBus
{
class Bus;
//--------------------------------------------------------------
class Node
{
friend class Bus;
public:
Node() = delete; // Default constructor
virtual ~Node(); // Default destructor
Node(const Node &obj) = delete; // Copy constructor
Node(Node &&obj) noexcept = delete; // Move constructor
Node &operator=(const Node &obj) = delete; // Copy assignment operator
Node &operator=(Node &&obj) noexcept = delete; // Move assignment operator
explicit Node(Bus &bus); // Constructor
// Event subscription management
void subscribe(const HashID &eventType); // Subscribe to a specific message type
void unsubscribe(const HashID &eventType); // Unsubscribe from a specific message type
void unsubscribeAll(); // Unsubscribe from all message types
[[nodiscard]] bool isSubscribed(const HashID &eventType) const; // Check if is subscribed to a specific message type
void syncWaitForMessage(); // Wait for a message to be received
// Data transmission
void postMessage(const std::shared_ptr<Message> &message) const; // Post a message to the bus
[[nodiscard]] std::chrono::microseconds getMessageMaxAge() const; // Get the maximum age of messages in the queue
[[nodiscard]] size_t getMessageCount() const; // Get the number of received messages
[[nodiscard]] std::shared_ptr<Message> popNextMessage(); // Get the next message from the queue
protected:
virtual void receiveMessageFromBus(std::shared_ptr<Message> message); // Receive a message from the bus (to be called by the bus when a message is posted)
void notifyMessageQueue(); // Notify the node of a new message from the bus
Bus &m_bus; // Reference to the bus this node is connected to
std::atomic_bool m_nodeWaitFlag = false; // Flag to control the node thread loop
struct
{
mutable std::mutex mtx; // Mutex for thread-safe access to the event queue
std::queue<std::shared_ptr<Message>> messageQueue; // Queue of received events
} m_dBusMessages;
};
//--------------------------------------------------------------
} // namespace dBus

39
src/gui/bars/menuBar.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include "menuBar.h"
using namespace std;
using namespace gui;
//--------------------------------------------------------------
MenuBar::MenuBar(wxFrame *parent)
{
// Initialization
parent->SetMenuBar(this);
// Creating menus
createMenus();
}
//--------------------------------------------------------------
/* Create menus */
void MenuBar::createMenus()
{
// File menu
const auto fileMenu = new wxMenu();
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_NEW), _("New\tCTRL+N"), _("Create a new project"));
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_OPEN), _("Open...\tCTRL+O"), _("Open a project file"));
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_SAVE), _("Save\tCTRL+S"), _("Save the project file"));
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_SAVE_AS), _("Save as...\tCTRL+SHIFT+S"), _("Save the project to a new file"));
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_CLOSE), _("Close\tCTRL+F4"), _("Close the project"));
fileMenu->AppendSeparator();
fileMenu->Append(static_cast<int>(IDs::ID_MENU_FILE_QUIT), _("Quit\tALT+F4"), _("Quit the application"));
Append(fileMenu, _("File"));
// Requirements menu
const auto reqMenu = new wxMenu();
reqMenu->Append(static_cast<int>(IDs::ID_MENU_REQ_CREATE), _("Create Requirements\tCTRL+R"), _("Create a new requirement"));
Append(reqMenu, _("Requirements"));
// Help menu
const auto helpMenu = new wxMenu();
helpMenu->Append(static_cast<int>(IDs::ID_MENU_HELP_ABOUT), _("About\tF1"), _("Show info about the application"));
Append(helpMenu, _("Help"));
}
//--------------------------------------------------------------

41
src/gui/bars/menuBar.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include <wx/wx.h>
namespace gui
{
//--------------------------------------------------------------
class MenuBar : public wxMenuBar
{
public:
enum class IDs : uint16_t
{
// File menu
ID_MENU_FILE_NEW = wxID_HIGHEST + 1, // File->New project
ID_MENU_FILE_OPEN, // File->Open project
ID_MENU_FILE_SAVE, // File->Save project
ID_MENU_FILE_SAVE_AS, // File->Save project as
ID_MENU_FILE_CLOSE, // File->Close project
ID_MENU_FILE_QUIT, // File->Quit
// Requirements menu
ID_MENU_REQ_CREATE, // Requirements->Add
// Help menu
ID_MENU_HELP_ABOUT, // Help->About
};
public:
MenuBar() = delete; // Default constructor
explicit MenuBar(wxFrame *parent); // Constructor
virtual ~MenuBar() = default; // Default destructor
MenuBar(MenuBar &obj) = delete; // Copy constructor
MenuBar(MenuBar &&obj) = delete; // Move constructor
MenuBar &operator=(const MenuBar &obj) = delete; // Copy assignment operator
MenuBar &operator=(MenuBar &&obj) = delete; // Move assignment operator
private:
void createMenus(); // Create menus
};
//--------------------------------------------------------------
} // namespace gui

View File

@@ -0,0 +1,11 @@
#include "statusBar.h"
using namespace std;
using namespace gui;
//--------------------------------------------------------------
StatusBar::StatusBar(wxFrame *parent)
: wxStatusBar(parent)
{
parent->SetStatusBar(this);
}
//--------------------------------------------------------------

20
src/gui/bars/statusBar.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <wx/wx.h>
namespace gui
{
//--------------------------------------------------------------
class StatusBar : public wxStatusBar
{
public:
StatusBar() = delete; // Default constructor
explicit StatusBar(wxFrame *parent); // Constructor
virtual ~StatusBar() = default; // Default destructor
StatusBar(StatusBar &obj) = delete; // Copy constructor
StatusBar(StatusBar &&obj) = delete; // Move constructor
StatusBar &operator=(const StatusBar &obj) = delete; // Copy assignment operator
StatusBar &operator=(StatusBar &&obj) = delete; // Move assignment operator
};
//--------------------------------------------------------------
} // namespace gui

23
src/gui/bars/toolBar.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "toolBar.h"
using namespace std;
using namespace gui;
//--------------------------------------------------------------
ToolBar::ToolBar(wxWindow *parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER)
{
// Initialization
const auto minSize = wxSize(-1, 64);
SetMinSize(minSize);
SetSize(minSize);
// Creating menus
createTools();
}
//--------------------------------------------------------------
/* Create tools */
void ToolBar::createTools()
{
}
//--------------------------------------------------------------

23
src/gui/bars/toolBar.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <wx/wx.h>
namespace gui
{
//--------------------------------------------------------------
class ToolBar : public wxPanel
{
public:
ToolBar() = delete; // Default constructor
explicit ToolBar(wxWindow *parent); // Constructor
virtual ~ToolBar() = default; // Default destructor
ToolBar(ToolBar &obj) = delete; // Copy constructor
ToolBar(ToolBar &&obj) = delete; // Move constructor
ToolBar &operator=(const ToolBar &obj) = delete; // Copy assignment operator
ToolBar &operator=(ToolBar &&obj) = delete; // Move assignment operator
private:
void createTools(); // Create tools
};
//--------------------------------------------------------------
} // namespace gui

151
src/gui/mainFrame.cpp Normal file
View File

@@ -0,0 +1,151 @@
#include "mainFrame.h"
#include "bars/menuBar.h"
#include "bars/statusBar.h"
#include "bars/toolBar.h"
/* --- */
#include "api/requirement.h"
/* --- */
using namespace std;
using namespace gui;
//--------------------------------------------------------------
/* Constructor */
MainFrame::MainFrame(dBus::Bus &bus)
: wxFrame(nullptr, wxID_ANY, _("kwa.Fr"), wxDefaultPosition, wxDefaultSize)
, m_bus(bus)
{
// Initialization
SetIcon(wxICON(AAAA_ICON));
const wxSize minSize(1200, 900);
SetMinSize(minSize);
SetSize(minSize);
// Creating controls
createControls();
// Post-initialization
m_menuBar->Bind(wxEVT_MENU, &MainFrame::on_menubarItemClick, this);
CenterOnScreen();
}
//--------------------------------------------------------------
/* Creating controls */
void MainFrame::createControls()
{
m_menuBar = new MenuBar(this);
m_statusBar = new StatusBar(this);
const auto framePanel = new wxPanel(this /*, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER*/);
m_toolBar = new ToolBar(framePanel);
m_pageNotebook = new wxNotebook(framePanel, wxID_ANY);
// Controls positioning
const auto mainSizer = new wxBoxSizer(wxVERTICAL);
mainSizer->Add(m_toolBar, wxSizerFlags(0).Expand());
mainSizer->Add(m_pageNotebook, wxSizerFlags(1).Expand().Border(wxALL, 5));
framePanel->SetSizer(mainSizer);
Layout();
}
//--------------------------------------------------------------
/* Create a new project */
void MainFrame::createProject()
{
try
{
const auto ret = postFileOperationRequest(m_bus, api::requirement::FileOperation::Create);
if (!ret)
{
wxMessageBox("No manager found for project creation request.\n\n"
"The manager responsible for handling project creation "
"did not respond to the request. Please ensure that "
"the project management component is running and try again.",
"Failed...",
wxOK | wxICON_WARNING,
this);
}
}
catch (const std::exception &e)
{
wxMessageBox("An unexpected error occurred while creating a new project\n\n" + wxString(e.what()),
"Critical Error",
wxOK | wxICON_ERROR,
this);
}
}
//--------------------------------------------------------------
/* Open an existing project */
void MainFrame::openProject()
{
}
//--------------------------------------------------------------
/* Save the current project */
void MainFrame::saveProject()
{
}
//--------------------------------------------------------------
/* Save the current project as... */
void MainFrame::saveProjectAs()
{
}
//--------------------------------------------------------------
/* Close the current project */
void MainFrame::closeProject() const
{
}
//--------------------------------------------------------------
/* Menu bar item click event */
void MainFrame::on_menubarItemClick(wxCommandEvent &event)
{
switch (static_cast<MenuBar::IDs>(event.GetId()))
{
using enum MenuBar::IDs;
// File menu
case ID_MENU_FILE_NEW:
{
createProject();
break;
}
case ID_MENU_FILE_OPEN:
{
openProject();
break;
}
case ID_MENU_FILE_SAVE:
{
saveProject();
break;
}
case ID_MENU_FILE_SAVE_AS:
{
saveProjectAs();
break;
}
case ID_MENU_FILE_CLOSE:
{
closeProject();
break;
}
case ID_MENU_FILE_QUIT:
{
Close();
break;
}
// Requirement menu
case ID_MENU_REQ_CREATE:
{
break;
}
// Help menu
default:
{
}
}
}
//--------------------------------------------------------------

48
src/gui/mainFrame.h Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <memory>
#include <wx/notebook.h>
#include <wx/wx.h>
#include <dBus/dBus.h>
namespace gui
{
class MenuBar;
class StatusBar;
class ToolBar;
//--------------------------------------------------------------
class MainFrame : public wxFrame
{
public:
MainFrame() = delete; // Default constructor
virtual ~MainFrame() = default; // Default destructor
MainFrame(MainFrame &obj) = delete; // Copy constructor
MainFrame(MainFrame &&obj) noexcept = delete; // Move constructor
MainFrame &operator=(const MainFrame &obj) = delete; // Copy assignment operator
MainFrame &operator=(MainFrame &&obj) noexcept = delete; // Move assignment operator
explicit MainFrame(dBus::Bus &bus); // Constructor
protected:
dBus::Bus &m_bus; // Reference to the application bus
MenuBar *m_menuBar = nullptr; // Menu bar handle
StatusBar *m_statusBar = nullptr; // Status bar handle
ToolBar *m_toolBar = nullptr; // Tool bar handle
wxNotebook *m_pageNotebook = nullptr; // Main notebook handle
private:
void createControls(); // Creating controls
void createProject(); // Create a new project
void openProject(); // Open an existing project
void saveProject(); // Save the current project
void saveProjectAs(); // Save the current project as...
void closeProject() const; // Close the current project
// Events
public:
void on_menubarItemClick(wxCommandEvent &event); // Menu bar item click event
};
//--------------------------------------------------------------
} // namespace gui

BIN
src/kwaFr.rc Normal file

Binary file not shown.

50
src/kwaFr_app.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "kwaFr_app.h"
#include "gui/mainFrame.h"
#include "misc/logConsoleGUI/logConsoleGUI.h"
#include <chrono>
#include <wx/image.h>
wxIMPLEMENT_APP(kwaFr_app);
using namespace std;
//--------------------------------------------------------------
/* Default destructor */
kwaFr_app::~kwaFr_app()
{
// Clean up the application context
m_coreManager.reset();
}
//--------------------------------------------------------------
bool kwaFr_app::OnInit()
{
// Initialize the application context
m_coreManager = make_unique<core::CoreManager>();
// Initialize console
logConsole = make_unique<sdiTools::logConsoleGUI>();
#ifdef DEBUG
constexpr auto attachOnly = false;
// logConsole->initConsole();
#else
constexpr auto attachOnly = true;
#endif
logConsole->initConsole(attachOnly);
// wxWidgets configuration
wxInitAllImageHandlers();
// Creating the main frame
const auto frame = new gui::MainFrame(m_coreManager->getBus());
frame->Show();
api::log::postLogMessage(m_coreManager->getBus(), "Application started");
return true;
}
//--------------------------------------------------------------
int kwaFr_app::OnExit()
{
// Application exit
return wxApp::OnExit();
}
//--------------------------------------------------------------

25
src/kwaFr_app.h Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include "core/coreManager.h"
#include <memory>
#include <wx/app.h>
//--------------------------------------------------------------
class kwaFr_app : public wxApp
{
public:
kwaFr_app() = default; // Default constructor
virtual ~kwaFr_app(); // Default destructor
kwaFr_app(const kwaFr_app &obj) = delete; // Copy constructor
kwaFr_app(kwaFr_app &&obj) noexcept = delete; // Move constructor
kwaFr_app &operator=(const kwaFr_app &obj) = delete; // Copy assignment operator
kwaFr_app &operator=(kwaFr_app &&obj) noexcept = delete; // Move assignment operator
bool OnInit() override;
int OnExit() override;
protected:
std::unique_ptr<core::CoreManager> m_coreManager; // Core manager instance
};
//--------------------------------------------------------------

15
src/misc/json/json.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
//--------------------------------------------------------------
#include "misc/json/nlohmann/json.hpp"
//--------------------------------------------------------------
using json = nlohmann::ordered_json;
//--------------------------------------------------------------
template<class T>
inline T json_readData(const json &jsonData, const std::string &key, const T &defaultValue = {})
{
if (jsonData.contains(key))
return jsonData.at(key).get<T>();
return defaultValue;
}
//--------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,176 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 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.11.3
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 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 != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
#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 11 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 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_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) json_abi ## a ## b
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
// 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,295 @@
/* --- logConsoleGUI.cpp ---------------------------------------
System: Log system for gui applications
Status: Version 1.0 Release 2
Language: C++
(c) Copyright SD-Innovation 2016
Author: Sylvain Schneider
------------------------------------------------------------- */
#include "logConsoleGUI.h"
#include <ctime>
#include <sstream>
#ifdef _WIN32
# include <Windows.h>
#endif // defined WIN32
t_logConsoleGUI logConsole;
using namespace std;
using namespace sdiTools;
//--------------------------------------------------------------
logConsoleGUI::logConsoleGUI(const std::string &logFilepath)
{
// Constructor
if (!logFilepath.empty())
openLogFile(logFilepath);
}
//--------------------------------------------------------------
logConsoleGUI::~logConsoleGUI()
{
// Destructor
releaseConsole();
}
//--------------------------------------------------------------
void logConsoleGUI::openLogFile(const std::string &logFilepath)
{
// Open log file
m_logFile.open(logFilepath.c_str(), std::ofstream::out | std::ofstream::app);
if (!m_logFile.is_open())
throw logic_error("Unable to open log file !");
}
//--------------------------------------------------------------
void logConsoleGUI::closeLogFile()
{
// Close log file
if (m_logFile.is_open())
m_logFile.close();
}
//--------------------------------------------------------------
int64_t logConsoleGUI::getLogFileSize()
{
// Return opened log file size
if (!m_logFile.is_open())
return -1;
return m_logFile.tellp();
}
//--------------------------------------------------------------
void logConsoleGUI::initConsole(bool attachOnly)
{
// Init application console and attach debug console under MSW
#ifdef _WIN32
if (hasAttachedConsole())
return;
bool hasParentConsole = true;
if (!AttachConsole(ATTACH_PARENT_PROCESS)) // Attach application to console
{
if (attachOnly)
return;
// No console was available
hasParentConsole = false;
if (!AllocConsole()) // Create console and attach application to it
{
// Error during creating console
throw logic_error("Unable to attach application to debug console");
}
}
int err;
err = freopen_s(&m_stdoutFile, "CONOUT$", "w", stdout);
if (err != 0)
{
// Error during reopen on stdout
throw logic_error("Unable to reopen stdout");
}
err = freopen_s(&m_stderrFile, "CONOUT$", "w", stderr);
if (err != 0)
{
// Error during reopen on stderr
throw logic_error("Unable to reopen stderr");
}
cout.clear();
cerr.clear();
if (hasParentConsole)
{
cout << endl;
}
// cout << "logConsoleGUI is ready !" << endl;
#endif // defined WIN32
}
//--------------------------------------------------------------
void logConsoleGUI::releaseConsole()
{
// Release application console
#ifdef _WIN32
if (!hasAttachedConsole())
return;
cout << "logConsoleGUI is terminated !" << endl;
cout.clear();
cerr.clear();
FreeConsole();
fclose(m_stdoutFile);
fclose(m_stderrFile);
#endif // defined WIN32
}
//--------------------------------------------------------------
bool logConsoleGUI::hasAttachedConsole()
{
// Returns true if console was attached, false otherwise
#ifdef _WIN32
if (GetConsoleWindow())
return true;
return false;
#endif // defined WIN32
return false;
}
//--------------------------------------------------------------
void logConsoleGUI::disable_quickEditMode()
{
#ifdef _WIN32
HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
if (hInput == INVALID_HANDLE_VALUE || hInput == NULL)
{
DWORD errorCode = GetLastError();
throw runtime_error("Unable to get input console handle");
}
BOOL bRet;
DWORD consoleMode;
bRet = GetConsoleMode(hInput, &consoleMode);
if (bRet == 0)
{
DWORD errorCode = GetLastError();
throw runtime_error("Unable to get console mode");
}
consoleMode = ENABLE_EXTENDED_FLAGS | (consoleMode & ~ENABLE_QUICK_EDIT_MODE);
bRet = SetConsoleMode(hInput, consoleMode);
if (bRet == 0)
{
// DWORD errorCode = GetLastError();
throw runtime_error("Unable to set console mode");
}
// Disable cursor
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOutput == INVALID_HANDLE_VALUE || hOutput == NULL)
{
DWORD errorCode = GetLastError();
throw runtime_error("Unable to get output console handle");
}
CONSOLE_CURSOR_INFO cursorInfo;
bRet = GetConsoleCursorInfo(hOutput, &cursorInfo);
if (bRet == 0)
{
throw runtime_error("Unable to get console cursor info");
}
cursorInfo.bVisible = false; // set the cursor visibility
bRet = SetConsoleCursorInfo(hOutput, &cursorInfo);
if (bRet == 0)
{
throw runtime_error("Unable to set console cursor info");
}
#endif // defined WIN32
}
//--------------------------------------------------------------
void logConsoleGUI::alwaysOnTop()
{
#ifdef _WIN32
HWND hWin = GetConsoleWindow();
if (!hWin)
throw runtime_error("Unable to get window handle");
BOOL bRet;
bRet = SetWindowPos(
hWin, // window handle
HWND_TOPMOST, // "handle to the window to precede
// the positioned window in the Z order
// OR one of the following:"
// HWND_BOTTOM or HWND_NOTOPMOST or HWND_TOP or HWND_TOPMOST
0,
80, // X, Y position of the window (in client coordinates)
0,
0, // cx, cy => width & height of the window in pixels
SWP_DRAWFRAME | /* SWP_NOMOVE | */ SWP_NOSIZE | SWP_SHOWWINDOW // The window sizing and positioning flags.
);
if (bRet == 0)
{
DWORD errorCode = GetLastError();
throw runtime_error("Unable to change window mode");
}
#endif // defined WIN32
}
//--------------------------------------------------------------
void logConsoleGUI::log(const std::string &msg, e_logLevel level)
{
// Log message into console and log file
string levelStr = "";
switch (level)
{
case level_DEBUG_COMMUNICATION:
#ifndef DEBUG
return;
#endif
levelStr = "[DEBUG_COMM] ";
break;
case level_DEBUG:
#ifndef DEBUG
return;
#endif
levelStr = "[DEBUG] ";
break;
case level_INFORMATIONS:
break;
case level_NOTICE:
levelStr = "[NOTICE] ";
break;
case level_WARNING:
levelStr = "[WARNING] ";
break;
case level_ERROR:
levelStr = "[ERROR] ";
break;
case level_CRITICAL:
levelStr = "[CRITICAL] ";
break;
case level_ALERT:
levelStr = "[ALERT] ";
break;
case level_EMERGENCY:
levelStr = "[EMERGENCY] ";
break;
}
string logMsg = header() + levelStr + msg;
cout << logMsg << endl;
if (m_logFile.is_open())
m_logFile << logMsg << endl;
}
//--------------------------------------------------------------
std::string logConsoleGUI::header()
{
// Define header to console log
char buff[20];
time_t now = time(nullptr);
struct tm tm;
#ifdef _WIN32
localtime_s(&tm, &now);
#elif __linux__
tm = *localtime(&now);
#endif
strftime(buff, 20, "%Y-%m-%d %H:%M:%S", &tm);
ostringstream oss;
oss << buff << " - ";
return oss.str();
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,91 @@
/* --- logConsoleGUI.h -----------------------------------------
System: Log system for gui applications
Status: Version 1.0 Release 2
Language: C++
(c) Copyright SD-Innovation 2016
Address:
SD-INNOVATION SAS
Site Eiffel Energie / ZAC Ban La Dame
48, square Eugène Herzog
54390 FROUARD
FRANCE
Author: Sylvain Schneider
E-Mail: s.schneider@sd-innovation.fr
Description: Header file for logConsoleGUI
This class allows you to attach GUI application to text console
or file to write log informations.
Thread Safe: No
Platform Dependencies: None (i.e.: Linux/Intel, IRIX/Mips, Solaris/SPARC)
Compiler Options:
Change History:
Date Author Description
2016-09-28 Sylvain Schneider Closing the console window when it released
2016-08-23 Sylvain Schneider First version
------------------------------------------------------------- */
#ifndef LOGCONSOLEGUI_H
#define LOGCONSOLEGUI_H
#include <iostream>
#include <fstream>
#include <memory>
#include <stdint.h>
enum e_logLevel
{
level_DEBUG_COMMUNICATION, // Information useful to developers for debugging the communication application
level_DEBUG, // Information useful to developers for debugging the application
level_INFORMATIONS, // Normal operational messages that require no action (An application has started, paused or ended successfully)
level_NOTICE, // Events that are unusual, but not error conditions
level_WARNING, // May indicate that an error will occur if action is not taken
level_ERROR, // Error conditions
level_CRITICAL, // Critical conditions
level_ALERT, // Should be corrected immediately
level_EMERGENCY, // Application is unusable
};
namespace sdiTools
{
//--------------------------------------------------------------
class logConsoleGUI
{
public:
public:
logConsoleGUI(const std::string &logFilepath = ""); // Constructor
virtual ~logConsoleGUI(); // Destructor
void openLogFile(const std::string &logFilepath); // Open log file
void closeLogFile(); // Close log file
int64_t getLogFileSize(); // Return opened log file size
void initConsole(bool attachOnly = false); // Init application console
void releaseConsole(); // Release application console
bool hasAttachedConsole(); // Returns true if console was attached, false otherwise
void disable_quickEditMode();
void alwaysOnTop();
void log(const std::string &msg, // Log message into console and log file
e_logLevel level = level_INFORMATIONS);
virtual std::string header(); // Define header to console log
private:
std::ofstream m_logFile;
FILE *m_stdoutFile = nullptr;
FILE *m_stderrFile = nullptr;
};
//--------------------------------------------------------------
} // namespace sdiTools
typedef std::unique_ptr<sdiTools::logConsoleGUI> t_logConsoleGUI;
extern t_logConsoleGUI logConsole;
#endif // LOGCONSOLEGUI_H

17
src/misc/tools.h Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
#include <chrono>
//--------------------------------------------------------------
inline uint64_t encodeTimepoint(const std::chrono::system_clock::time_point &timepoint)
{
const auto duration = timepoint.time_since_epoch();
return static_cast<uint64_t>(duration.count());
}
//--------------------------------------------------------------
inline std::chrono::system_clock::time_point decodeTimepoint(const uint64_t value)
{
const auto duration = std::chrono::system_clock::duration(static_cast<std::chrono::seconds>(value));
return std::chrono::system_clock::time_point(duration);
}
//--------------------------------------------------------------

16
src/misc/wxCustom.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <wx/statline.h>
#include <wx/wx.h>
//--------------------------------------------------------------
inline wxStaticLine *createHorizontalLine(wxWindow *parent, const wxWindowID id = wxID_ANY)
{
return new wxStaticLine(parent, id, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL);
}
//--------------------------------------------------------------
inline wxStaticLine *createVerticalLine(wxWindow *parent, const wxWindowID id = wxID_ANY)
{
return new wxStaticLine(parent, id, wxDefaultPosition, wxDefaultSize, wxLI_VERTICAL);
}
//--------------------------------------------------------------

BIN
src/resources/appIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
src/resources/bin2c.exe Normal file

Binary file not shown.

26
src/resources/resources.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <wx/animate.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/mstream.h>
//--------------------------------------------------------------
//--------------------------------------------------------------
#define wxGetBitmapFromMemory(name) _wxGetBitmapFromMemory(name, sizeof(name))
inline wxBitmap _wxGetBitmapFromMemory(const unsigned char *data, int length)
{
wxMemoryInputStream is(data, length);
return { wxImage(is, wxBITMAP_TYPE_ANY, -1), -1 };
}
//--------------------------------------------------------------
#define wxGetAnimationFromMemory(name) _wxGetAnimationFromMemory(name, sizeof(name))
inline wxAnimation _wxGetAnimationFromMemory(const unsigned char *data, int length)
{
wxAnimation animate;
wxMemoryInputStream is(data, length);
animate.Load(is);
return animate;
}
//--------------------------------------------------------------

View File

@@ -0,0 +1,13 @@
[buildInformation]
buildDate = 2022-10-14 09:36:16
buildNumber = 155
companyName = SD-Innovation S.A.S.
copyright = Copyright SDi (C) 2019
fileDecription = Aggregometer application manager
internalName = thromboSoft.exe
majorRevision = 0
majorVersion = 2
minorRevision = 0
minorVersion = 3
originalFilename = thromboSoft.exe
productName = ThromboSoft

13
src/version/appVersion.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
#include <string_view>
namespace version
{
//--------------------------------------------------------------
static constexpr std::string_view appName = "ThromboSoft";
static constexpr std::string_view fullVersion = "2.0.3.0 debugAndTests";
static constexpr std::string_view shortVersion = "2.0.3.0 debugAndTests";
//--------------------------------------------------------------
} // namespace version

19
src/version/appVersion.rc Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#define COMPANY_NAME "SD-Innovation S.A.S."
#define PRODUCT_NAME "ThromboSoft"
#define FILE_DESCRIPTION "ThromboAggregometer application manager"
#define ORIGINAL_FILENAME "thromboSoft.exe"
#define INTERNAL_NAME "thromboSoft.exe"
#define COPYRIGHT "Copyright SDi (C) 2024"
#define FULL_VERSION "2.0.3.0"
#define LONG_VERSION "2.0.3.0"
#define SHORT_VERSION "2.0"
#define BUILD_DATE "2024-10-25 16:10:19"
#define BUILD_NUMBER 435
#define MAJOR_VERSION 2
#define MINOR_VERSION 0
#define MAJOR_REVISION 3
#define MINOR_REVISION 0
//#endif // APPVERSION_20221014093616_H

BIN
src/version/autoVersion.exe Normal file

Binary file not shown.

BIN
src/version/semverApp.exe Normal file

Binary file not shown.

14
src/version/version.xml Normal file
View File

@@ -0,0 +1,14 @@
<SDi_semver Version="1.0.0">
<General>
<companyName>SD Innovation S.A.S.</companyName>
<productName>SDi semVer</productName>
<copyright>(c) SDi 2023</copyright>
</General>
<Build>
<major>0</major>
<minor>0</minor>
<patch>0</patch>
<build>144</build>
<buildDate>1698656154</buildDate>
</Build>
</SDi_semver>