diff --git a/.clang-format b/.clang-format index e7fd784dccff38f5515e53349eb2c918c770770e..778726d25a4e606f9d50d65846e00c649f3649b9 100644 --- a/.clang-format +++ b/.clang-format @@ -62,6 +62,7 @@ CommentPragmas: '^ KEEP pragma:' ForEachMacros: - 'for_each' - 'for_ever' + - 'forever' - 'parallel_for' - 'script_class' - 'function_class' diff --git a/PreCompiledHeaders.cmake b/PreCompiledHeaders.cmake index 1de55147c549a687610fcada06e1d4d5cd19d2be..e0a4d70d639d15cec9971433e9744d44b9a3af31 100644 --- a/PreCompiledHeaders.cmake +++ b/PreCompiledHeaders.cmake @@ -36,6 +36,7 @@ set(EXT_INC PRIVATE set(QT_STRUCT_INC PRIVATE <qglobal.h> + <QThread> <QtGlobal> <QObject> <QRegularExpression> diff --git a/src/Lib/Log.cc b/src/Lib/Log.cc index db13553bebf15eef33479fabcd5d0ba6a47be9e0..245a8df13a5bab15490a249bc809fc041333b0e5 100644 --- a/src/Lib/Log.cc +++ b/src/Lib/Log.cc @@ -10,6 +10,28 @@ LogLevel::toStdStringView(const LogLevel::Level lvl) noexcept } } +// LogSinkUpdater implementations +namespace Vivy +{ +StlLogSinkUpdater::StlLogSinkUpdater(MutexType *const lock, MessageQueueType *const queue) noexcept + : updater(lock, queue) +{ + setPriority(QThread::LowestPriority); +} + +void +StlLogSinkUpdater::run() noexcept +{ + forever { + if (QThread::currentThread()->isInterruptionRequested()) + return; + if (updater.isMessageAvailable()) + emit messageAvailable(); + usleep(chrono::microseconds(10000).count()); // Sleeps for 0.1s + } +} +} + // Vivy::LogSinkDispatcher and child classes implementation namespace Vivy { @@ -54,8 +76,24 @@ StderrLogSinkDispatcher::handleLogMessage(const std::string_view category, // Vivy::LogSink implementation namespace Vivy { +LogSink::LogSink() noexcept +{ + workerThread = new StlLogSinkUpdater(&messageQueueLock, &messageQueue); + connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater); + connect( + workerThread, &StlLogSinkUpdater::messageAvailable, this, [this]() { flush(); }, + Qt::QueuedConnection); + + workerThread->start(); +} + // Flush all messages before exiting -LogSink::~LogSink() noexcept { flush(); } +LogSink::~LogSink() noexcept +{ + workerThread->requestInterruption(); + workerThread->wait(); + flush(); +} // Get the log message from the logger and add it to the sink's queue. void diff --git a/src/Lib/Log.hh b/src/Lib/Log.hh index 31b0ebc837e536c4c6f6f9c7489dbcad4882ae8b..0cfcc0d580f299d07bcf19a4c358a62ac3144ef3 100644 --- a/src/Lib/Log.hh +++ b/src/Lib/Log.hh @@ -51,15 +51,6 @@ #define VIVY_LOG_QUOTED(something) '\'' << (something) << '\'' -// Install logger, use the global LogSink -#define VIVY_APP_LOGGABLE_OBJECT(name, logger) \ - VIVY_LOGGABLE_OBJECT(vivyApp->getLogSink(), name, logger) - -// Install logger, use the global LogSink, with a stored name like in the -// VIVY_LOGGABLE_OBJECT_BY_STORED_NAME macro. -#define VIVY_APP_LOGGABLE_OBJECT_BY_STORED_NAME(name, logger) \ - VIVY_LOGGABLE_OBJECT_BY_STORED_NAME(vivyApp->getLogSink(), name, logger) - namespace Vivy { class LogSinkDispatcher; @@ -70,6 +61,7 @@ class LogMessage; // The severity of an event. Critical will cause the LogSink to flush all // messages to its emeters and then abort. struct LogLevel final { + // NOTE: The order matters: enum Level : int { None, // In option setup to disable logs Debug, @@ -84,6 +76,8 @@ struct LogLevel final { private: using Array = std::array<const std::string_view, ___MaxAndUnused>; + + // WARN: The order matters! static inline constexpr Array LevelsStringViews = { "None", "Debug", "Info", "Warnin", "Error", "Critical" }; @@ -122,17 +116,63 @@ public: void handleLogMessage(const std::string_view, const LogMessage &) noexcept override; }; +// Class used to check the messageQueue and send needFlush or something like +// that messages. It needs to be moved to its own thread and connected with the +// queue version of the QObject::connect method. +template <class MutexType, class MessageQueueType> class LogSinkUpdater final { + MutexType *const messageQueueLock; + MessageQueueType *const messageQueue; + + using size_type = typename MessageQueueType::size_type; + +public: + explicit LogSinkUpdater(MutexType *const lock, MessageQueueType *const queue) noexcept + : messageQueueLock(lock) + , messageQueue(queue) + { + } + + bool isMessageAvailable() const noexcept + { + std::lock_guard<MutexType> lockGuard(*messageQueueLock); + size_type size = messageQueue->size(); + return size != 0; + } +}; + +// LogSinkUpdater controller, needed because templated Q_OBJECT are not +// supported by Qt. This controller is specialized for a std implementation of +// the message queue. +class StlLogSinkUpdater final : public QThread { + Q_OBJECT + +public: + using MutexType = std::mutex; + using MessageQueueType = std::vector<std::tuple<const std::string_view, LogMessage>>; + + explicit StlLogSinkUpdater(MutexType *const, MessageQueueType *const) noexcept; + + void run() noexcept override; + +private: + LogSinkUpdater<MutexType, MessageQueueType> updater; + +signals: + void messageAvailable(); +}; + // LogSink is the parent logger. It will recieve messages and display them to a // console or to std::cerr or to a file, etc. The save to over file, etc will // be donne by the LogSinkDispatcher. -class LogSink { +class LogSink : QObject { + Q_OBJECT VIVY_UNMOVABLE_OBJECT(LogSink) - explicit LogSink() noexcept = default; + explicit LogSink() noexcept; public: static std::shared_ptr<LogSink> newSink() noexcept; - ~LogSink() noexcept; + ~LogSink() noexcept override; void registerLogDispatcher(std::shared_ptr<LogSinkDispatcher>) noexcept; void registerLogger(std::shared_ptr<Logger>) noexcept; @@ -156,6 +196,7 @@ public: } private: + StlLogSinkUpdater *workerThread; std::mutex messageQueueLock{}; std::vector<std::tuple<const std::string_view, LogMessage>> messageQueue; std::vector<std::shared_ptr<LogSinkDispatcher>> logDispatchers; diff --git a/src/UI/FakeVim/FakeVimHandler.cc b/src/UI/FakeVim/FakeVimHandler.cc index 3d455d8b3b63e5900169c59ab00a53fd737fdb12..c1f327d91972dbdfef6e502aa1df706378c5089d 100644 --- a/src/UI/FakeVim/FakeVimHandler.cc +++ b/src/UI/FakeVim/FakeVimHandler.cc @@ -6156,8 +6156,7 @@ FakeVimHandler::Private::handleExMapCommand(const ExCommand &cmd0) // :map QString args = cmd0.args; bool silent = false; bool unique = false; - forever - { + forever { if (eatString("<silent>", &args)) { silent = true; } else if (eatString("<unique>", &args)) { diff --git a/src/VivyApplication.hh b/src/VivyApplication.hh index 543ce040d094965db4fc60185bc4ac60f2dae540..18b9cacec08da43437758cdca2e0662551ac4a52 100644 --- a/src/VivyApplication.hh +++ b/src/VivyApplication.hh @@ -4,8 +4,18 @@ #error "This is a C++ header" #endif +// Get the VivyApplication pointer #define vivyApp (static_cast<::Vivy::VivyApplication *>(VivyApplication::instance())) +// Install logger, use the global LogSink +#define VIVY_APP_LOGGABLE_OBJECT(name, logger) \ + VIVY_LOGGABLE_OBJECT(vivyApp->getLogSink(), name, logger) + +// Install logger, use the global LogSink, with a stored name like in the +// VIVY_LOGGABLE_OBJECT_BY_STORED_NAME macro. +#define VIVY_APP_LOGGABLE_OBJECT_BY_STORED_NAME(name, logger) \ + VIVY_LOGGABLE_OBJECT_BY_STORED_NAME(vivyApp->getLogSink(), name, logger) + #define currentVivyDocument() dynamic_cast<::Vivy::VivyDocument *>(vivyApp->getCurrentDocument()) #define currentScriptDocument dynamic_cast<::Vivy::ScriptDocument *>(vivyApp->getCurrentDocument())