2026-02-20 13:02:33 +03:00

156 lines
4.6 KiB
C++

export module utils:worker;
import :lockfree;
import :logger;
import std;
export namespace ee_utils {
struct WorkerCommand {
std::uint8_t type = 0;
};
template <typename T> concept IWorkerCommand = std::is_base_of_v<WorkerCommand, T>;
template <IWorkerCommand TCommand, std::size_t MaxCmdTypeId, bool IdleDetection = false>
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<std::function<void(TCommand& command)>, MaxCmdTypeId + 1> commandsMapping;
// TODO: maybe I should use lockfree::queue ? Otherwise make consumer thread explicit for safety
boost::lockfree::spsc_queue<TCommand, boost::lockfree::capacity<512>> 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<bool> 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 <typename T> concept IThreadedWorker = std::is_base_of_v<ThreadedWorkerBase, T>;
enum class IdleBackoffMode : std::uint8_t {
DISABLED,
YIELD
};
template <IWorkerCommand TCommand, std::size_t MaxCmdType, IdleBackoffMode IdleBackoffMode =
IdleBackoffMode::DISABLED>
class ThreadedWorker : public Worker<TCommand, MaxCmdType, static_cast<bool>(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);
}
};
}