export module utils:worker; import :lockfree; import :logger; import std; export namespace ee_utils { struct WorkerCommand { std::uint8_t type = 0; }; template concept IWorkerCommand = std::is_base_of_v; template class Worker { public: void Request(const TCommand& command) { commands.push(command); //TODO: filled queue case } void Request(const std::uint8_t type) { commands.push(TCommand{.type = type}); //TODO: filled queue case } void ProcessQueue(TCommand& buffer) requires !IdleDetection { while (this->commands.pop(buffer)) { ProcessCommand(buffer); } } std::uint32_t ProcessQueue(TCommand& buffer) requires IdleDetection { thread_local std::uint32_t idleCounter = 0; if (!this->commands.pop(buffer)) { idleCounter++; return idleCounter; } idleCounter = 0; do { ProcessCommand(buffer); } while (this->commands.pop(buffer)); return 0; } protected: // TODO: replace std::function with more optimized ref std::array, MaxCmdTypeId + 1> commandsMapping; // TODO: maybe I should use lockfree::queue ? Otherwise make consumer thread explicit for safety boost::lockfree::spsc_queue> commands; Worker() {}; void ProcessCommand(TCommand& command) { if (command.type > MaxCmdTypeId || !commandsMapping[command.type]) { LOG(WARNING, "Unhandled command. Type id: %d", command.type); command.type = MaxCmdTypeId; return; } commandsMapping[command.type](command); } }; class ThreadedWorkerBase { public: std::atomic isRunning; virtual ~ThreadedWorkerBase() {} void Start() { if (isRunning.load()) { LOG(CRITICAL, "Thread can be started only once before stop"); throw std::logic_error("Thread can be started only once before stop"); } isRunning.store(true); thread = std::jthread([this](std::stop_token stop) { this->stoken = &stop; Threading(); }); } void Shutdown() { if (isRunning.load()) thread.request_stop(); else LOG(WARNING, "Tried to shutdown stopped thread"); } protected: std::stop_token *stoken{}; std::jthread thread; virtual void Threading() = 0; }; template concept IThreadedWorker = std::is_base_of_v; enum class IdleBackoffMode : std::uint8_t { DISABLED, YIELD }; template class ThreadedWorker : public Worker(IdleBackoffMode)>, public ThreadedWorkerBase { protected: const wchar_t *threadName; explicit ThreadedWorker(const wchar_t *threadName, std::uint32_t yieldIdleTime = 0) { this->threadName = threadName; this->yieldIdleTime = yieldIdleTime; } void Threading() { Logger::RegisterThread(threadName); LOG(INFO, "Thread started"); Loop(); ReleaseStop(); } void Loop() { TCommand commandBuffer{}; while (!stoken->stop_requested()) { if constexpr (IdleBackoffMode == IdleBackoffMode::DISABLED) { this->ProcessQueue(commandBuffer); } else { const std::uint32_t idleCounter = this->ProcessQueue(commandBuffer); if (idleCounter > yieldIdleTime) std::this_thread::yield(); } Update(); } } virtual void Update() = 0; virtual void Release() = 0; private: std::uint32_t yieldIdleTime; void ReleaseStop() { Release(); LOG(INFO, "Thread is released"); Logger::UnregisterThread(); isRunning.store(false); } }; }