Working on multi-threading, including web

This commit is contained in:
Vladislav Khorev 2026-01-09 15:24:01 +03:00
parent b055faf882
commit 648acf8938
12 changed files with 230 additions and 45 deletions

View File

@ -474,6 +474,8 @@ add_executable(space-game001
src/planet/PlanetData.h src/planet/PlanetData.h
src/utils/Perlin.cpp src/utils/Perlin.cpp
src/utils/Perlin.h src/utils/Perlin.h
src/utils/TaskManager.cpp
src/utils/TaskManager.h
src/planet/StoneObject.cpp src/planet/StoneObject.cpp
src/planet/StoneObject.h src/planet/StoneObject.h
src/render/FrameBuffer.cpp src/render/FrameBuffer.cpp
@ -491,6 +493,7 @@ set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT s
target_include_directories(space-game001 PRIVATE target_include_directories(space-game001 PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/external" "${CMAKE_CURRENT_SOURCE_DIR}/external"
"C:/Boost/include/boost-1_84"
) )
set_target_properties(space-game001 PROPERTIES set_target_properties(space-game001 PROPERTIES
@ -501,6 +504,7 @@ set_target_properties(space-game001 PROPERTIES
# PNG_ENABLED включает код PNG в TextureManager # PNG_ENABLED включает код PNG в TextureManager
# SDL_MAIN_HANDLED отключает переопределение main -> SDL_main # SDL_MAIN_HANDLED отключает переопределение main -> SDL_main
target_compile_definitions(space-game001 PRIVATE target_compile_definitions(space-game001 PRIVATE
WIN32_LEAN_AND_MEAN
PNG_ENABLED PNG_ENABLED
SDL_MAIN_HANDLED SDL_MAIN_HANDLED
SIMPLIFIED SIMPLIFIED

View File

@ -157,7 +157,7 @@ emrun --no_browser --port 8080 .
# Emscripten new # Emscripten new
``` ```
emcc src/main.cpp src/Game.cpp src/Environment.cpp src/BoneAnimatedModel.cpp src/TextModel.cpp src/Projectile.cpp src/SparkEmitter.cpp src/UiManager.cpp src/render/Renderer.cpp src/render/ShaderManager.cpp src/render/TextureManager.cpp src/render/FrameBuffer.cpp src/render/OpenGlExtensions.cpp src/utils/Utils.cpp src/utils/Perlin.cpp src/planet/PlanetData.cpp src/planet/PlanetObject.cpp src/planet/StoneObject.cpp -O2 -std=c++17 -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=4 -sTOTAL_MEMORY=4294967296 -sINITIAL_MEMORY=3221225472 -sMAXIMUM_MEMORY=4294967296 -sALLOW_MEMORY_GROWTH=1 -I./thirdparty1/eigen-5.0.0 -I./src -I./thirdparty/libzip-1.11.3/build-emcmake/install/include -L./thirdparty/libzip-1.11.3/build-emcmake/install/lib -lzip -lz -sUSE_SDL_IMAGE=2 -sUSE_SDL=2 -sUSE_LIBPNG=1 -DSIMPLIFIED=1 --preload-file space-game001x.zip -o space-game001.html emcc src/main.cpp src/Game.cpp src/Environment.cpp src/BoneAnimatedModel.cpp src/TextModel.cpp src/Projectile.cpp src/SparkEmitter.cpp src/UiManager.cpp src/render/Renderer.cpp src/render/ShaderManager.cpp src/render/TextureManager.cpp src/render/FrameBuffer.cpp src/render/OpenGlExtensions.cpp src/utils/Utils.cpp src/utils/TaskManager.cpp src/utils/Perlin.cpp src/planet/PlanetData.cpp src/planet/PlanetObject.cpp src/planet/StoneObject.cpp -O2 -std=c++17 -pthread -sUSE_PTHREADS=1 -sPTHREAD_POOL_SIZE=4 -sTOTAL_MEMORY=4294967296 -sINITIAL_MEMORY=3221225472 -sMAXIMUM_MEMORY=4294967296 -sALLOW_MEMORY_GROWTH=1 -fexceptions -I./thirdparty1/eigen-5.0.0 -I./src -I./thirdparty/libzip-1.11.3/build-emcmake/install/include -IC:/Boost/include/boost-1_84 -L./thirdparty/libzip-1.11.3/build-emcmake/install/lib -lzip -lz -sUSE_SDL_IMAGE=2 -sUSE_SDL=2 -sUSE_LIBPNG=1 -DSIMPLIFIED=1 --preload-file space-game001.zip -o space-game001.html
``` ```
# License # License

View File

@ -13,7 +13,7 @@
namespace ZL namespace ZL
{ {
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
const char* CONST_ZIP_FILE = "space-game001a.zip"; const char* CONST_ZIP_FILE = "space-game001.zip";
#else #else
const char* CONST_ZIP_FILE = ""; const char* CONST_ZIP_FILE = "";
#endif #endif
@ -343,6 +343,8 @@ namespace ZL
std::cout << "Init step 6 " << std::endl; std::cout << "Init step 6 " << std::endl;
planetObject.init(); planetObject.init();
planetObject.taskManager = &taskManager;
std::cout << "Init step 7 " << std::endl; std::cout << "Init step 7 " << std::endl;
//rockTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); //rockTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE));

View File

@ -7,6 +7,8 @@
#include "planet/PlanetObject.h" #include "planet/PlanetObject.h"
#include "UiManager.h" #include "UiManager.h"
#include "Projectile.h" #include "Projectile.h"
#include "utils/TaskManager.h"
#include <queue>
namespace ZL { namespace ZL {
@ -103,6 +105,11 @@ namespace ZL {
uint64_t lastProjectileFireTime = 0; uint64_t lastProjectileFireTime = 0;
int maxProjectiles = 32; int maxProjectiles = 32;
std::vector<Vector3f> shipLocalEmissionPoints; std::vector<Vector3f> shipLocalEmissionPoints;
TaskManager taskManager;
}; };

View File

@ -51,12 +51,9 @@ namespace ZL {
planetMeshLods[i].Move(PLANET_CENTER_OFFSET); planetMeshLods[i].Move(PLANET_CENTER_OFFSET);
} }
//#ifndef SIMPLIFIED
planetAtmosphereLod = generateSphere(5, 0); planetAtmosphereLod = generateSphere(5, 0);
planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03); planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03);
planetAtmosphereLod.Move(PLANET_CENTER_OFFSET); planetAtmosphereLod.Move(PLANET_CENTER_OFFSET);
//#endif
} }
const LodLevel& PlanetData::getLodLevel(int level) const { const LodLevel& PlanetData::getLodLevel(int level) const {

View File

@ -26,7 +26,7 @@ namespace ZL {
VertexID generateEdgeID(const VertexID& id1, const VertexID& id2); VertexID generateEdgeID(const VertexID& id1, const VertexID& id2);
#ifdef SIMPLIFIED #ifdef SIMPLIFIED
constexpr static int MAX_LOD_LEVELS = 1; constexpr static int MAX_LOD_LEVELS = 6;
#else #else
constexpr static int MAX_LOD_LEVELS = 6; constexpr static int MAX_LOD_LEVELS = 6;
#endif #endif

View File

@ -4,7 +4,7 @@
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#include "Environment.h" #include "Environment.h"
#include "StoneObject.h" #include "StoneObject.h"
#include "utils/TaskManager.h"
namespace ZL { namespace ZL {
@ -77,20 +77,18 @@ namespace ZL {
sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/sand2.png", CONST_ZIP_FILE)); sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/sand2.png", CONST_ZIP_FILE));
stoneTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); stoneTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE));
//#ifndef SIMPLIFIED
// Атмосфера // Атмосфера
planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData;
if (planetAtmosphereRenderStruct.data.PositionData.size() > 0) if (planetAtmosphereRenderStruct.data.PositionData.size() > 0)
{ {
planetAtmosphereRenderStruct.RefreshVBO(); planetAtmosphereRenderStruct.RefreshVBO();
} }
//#endif
planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex)); planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex));
stonesToRender = planetStones.inflate(planetStones.allInstances.size()); //stonesToRender = planetStones.inflate(planetStones.allInstances.size());
stonesToRender.resize(planetStones.allInstances.size());
planetStones.initStatuses();
stoneToBake = planetStones.inflateOne(0, 0.75); stoneToBake = planetStones.inflateOne(0, 0.75);
} }
@ -98,6 +96,75 @@ namespace ZL {
void PlanetObject::update(float deltaTimeMs) { void PlanetObject::update(float deltaTimeMs) {
// 1. Проверка порога движения (оптимизация из текущего кода)
float movementThreshold = 1.0f;
if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold
&& !triangleIndicesToDraw.empty()) {
processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач
return;
}
lastUpdatePos = Environment::shipPosition;
// 2. Получаем список видимых треугольников
auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition);
std::sort(newIndices.begin(), newIndices.end());
// 3. Анализируем, что нужно загрузить
for (int triIdx : newIndices) {
if (planetStones.statuses[triIdx] == ChunkStatus::Empty) {
// Помечаем, чтобы не спамить задачами
planetStones.statuses[triIdx] = ChunkStatus::Generating;
// Отправляем тяжелую математику в TaskManager
taskManager->EnqueueBackgroundTask([this, triIdx]() {
// Выполняется в фоновом потоке: только генерация геометрии в RAM
float scaleModifier = 1.0f;
VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier);
// Передаем задачу на загрузку в GPU в очередь главного потока
this->EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable {
// Проверяем актуальность: если треугольник всё еще в списке видимых
auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx);
if (it != triangleIndicesToDraw.end()) {
stonesToRender[triIdx].data = std::move(data);
stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов
planetStones.statuses[triIdx] = ChunkStatus::Live;
}
else {
// Если уже не нужен — просто сбрасываем статус
planetStones.statuses[triIdx] = ChunkStatus::Empty;
}
});
});
}
}
// 4. Очистка (Unload)
// Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память
for (size_t i = 0; i < planetStones.statuses.size(); ++i) {
if (planetStones.statuses[i] == ChunkStatus::Live) {
bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i);
if (!isVisible) {
// Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers)
stonesToRender[i].data.PositionData.clear();
stonesToRender[i].vao.reset();
stonesToRender[i].positionVBO.reset();
stonesToRender[i].normalVBO.reset();
stonesToRender[i].tangentVBO.reset();
stonesToRender[i].binormalVBO.reset();
stonesToRender[i].colorVBO.reset();
stonesToRender[i].texCoordVBO.reset();
planetStones.statuses[i] = ChunkStatus::Empty;
}
}
}
triangleIndicesToDraw = std::move(newIndices);
// 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.)
processMainThreadTasks();
/*
float movementThreshold = 1.0f; float movementThreshold = 1.0f;
if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) {
return; return;
@ -111,7 +178,7 @@ namespace ZL {
if (newIndices != triangleIndicesToDraw) { if (newIndices != triangleIndicesToDraw) {
triangleIndicesToDraw = std::move(newIndices); triangleIndicesToDraw = std::move(newIndices);
} }*/
} }
@ -205,7 +272,6 @@ namespace ZL {
void PlanetObject::draw(Renderer& renderer) { void PlanetObject::draw(Renderer& renderer) {
//#ifndef SIMPLIFIED
{ {
if (stoneMapFB == nullptr) if (stoneMapFB == nullptr)
{ {
@ -221,10 +287,6 @@ namespace ZL {
stoneMapFB->GenerateMipmaps(); stoneMapFB->GenerateMipmaps();
} }
//#endif
//bakeStoneTexture(renderer);
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
//-------------------------- //--------------------------
@ -232,10 +294,7 @@ namespace ZL {
drawPlanet(renderer); drawPlanet(renderer);
drawStones(renderer); drawStones(renderer);
//#ifndef SIMPLIFIED
drawAtmosphere(renderer); drawAtmosphere(renderer);
//#endif
} }
void PlanetObject::drawPlanet(Renderer& renderer) void PlanetObject::drawPlanet(Renderer& renderer)
@ -382,7 +441,7 @@ namespace ZL {
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
/*
for (int i : triangleIndicesToDraw) for (int i : triangleIndicesToDraw)
//for (int i = 0; i < stonesToRender.size(); i++) //for (int i = 0; i < stonesToRender.size(); i++)
{ {
@ -390,7 +449,20 @@ namespace ZL {
{ {
renderer.DrawVertexRenderStruct(stonesToRender[i]); renderer.DrawVertexRenderStruct(stonesToRender[i]);
} }
}*/
for (int i : triangleIndicesToDraw) {
// КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ:
// Проверяем, что данные не просто существуют, а загружены в GPU
if (planetStones.statuses[i] == ChunkStatus::Live) {
// Дополнительная проверка на наличие данных (на всякий случай)
if (stonesToRender[i].data.PositionData.size() > 0) {
renderer.DrawVertexRenderStruct(stonesToRender[i]);
} }
}
// Если статус Generating или Empty — мы просто пропускаем этот индекс.
// Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO.
}
CheckGlError(); CheckGlError();
glDisable(GL_BLEND); glDisable(GL_BLEND);
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);

View File

@ -16,8 +16,11 @@
#include "PlanetData.h" #include "PlanetData.h"
#include "StoneObject.h" #include "StoneObject.h"
#include "render/FrameBuffer.h" #include "render/FrameBuffer.h"
#include <queue>
#include <mutex>
namespace ZL { namespace ZL {
class TaskManager;
class PlanetObject { class PlanetObject {
public: public:
@ -39,6 +42,8 @@ namespace ZL {
Vector3f lastUpdatePos; Vector3f lastUpdatePos;
TaskManager* taskManager = nullptr;
public: public:
PlanetObject(); PlanetObject();
@ -51,6 +56,32 @@ namespace ZL {
void drawAtmosphere(Renderer& renderer); void drawAtmosphere(Renderer& renderer);
float distanceToPlanetSurface(const Vector3f& viewerPosition); float distanceToPlanetSurface(const Vector3f& viewerPosition);
std::queue<std::function<void()>> mainThreadTasks;
std::mutex mainThreadMutex;
void EnqueueMainThreadTask(std::function<void()> task) {
std::lock_guard<std::mutex> lock(mainThreadMutex);
mainThreadTasks.push(task);
}
// Выполнение задач по одной (или пачкой) за кадр
void processMainThreadTasks() {
std::function<void()> task;
// Извлекаем только одну задачу, чтобы не блокировать update надолго
{
std::lock_guard<std::mutex> lock(mainThreadMutex);
if (!mainThreadTasks.empty()) {
task = std::move(mainThreadTasks.front());
mainThreadTasks.pop();
}
}
if (task) {
task(); // Здесь выполняется RefreshVBO или загрузка текстуры
}
}
}; };
} // namespace ZL } // namespace ZL

View File

@ -16,13 +16,20 @@ namespace ZL {
#ifdef SIMPLIFIED #ifdef SIMPLIFIED
const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня
const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси
const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси
const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины
const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины
const int StoneParams::STONES_PER_TRIANGLE = 40;
/*
const float StoneParams::BASE_SCALE = 1000.0f; // Общий размер камня const float StoneParams::BASE_SCALE = 1000.0f; // Общий размер камня
const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси
const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси
const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины
const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины
const int StoneParams::STONES_PER_TRIANGLE = 2; const int StoneParams::STONES_PER_TRIANGLE = 2;
*/
#else #else
const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня
const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси
@ -297,34 +304,12 @@ namespace ZL {
std::vector<VertexRenderStruct> StoneGroup::inflate(int count) std::vector<VertexRenderStruct> StoneGroup::inflate(int count)
{ {
//static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337);
std::vector<VertexRenderStruct> result; std::vector<VertexRenderStruct> result;
result.resize(count); result.resize(count);
for (int tIdx = 0; tIdx < count; tIdx++) for (int tIdx = 0; tIdx < count; tIdx++)
{ {
result[tIdx] = inflateOne(tIdx, 1.0f); result[tIdx] = inflateOne(tIdx, 1.0f);
/*
for (const auto& inst : allInstances[tIdx]) {
Matrix3f rotMat = QuatToMatrix(inst.rotation);
for (size_t j = 0; j < baseStone.PositionData.size(); ++j) {
Vector3f p = baseStone.PositionData[j];
Vector3f n = baseStone.NormalData[j];
p.v[0] *= inst.scale.v[0];
p.v[1] *= inst.scale.v[1];
p.v[2] *= inst.scale.v[2];
result[tIdx].data.PositionData.push_back(MultMatrixVector(rotMat, p) + inst.position);
result[tIdx].data.NormalData.push_back(MultMatrixVector(rotMat, n));
result[tIdx].data.TexCoordData.push_back(baseStone.TexCoordData[j]);
}
result[tIdx].RefreshVBO();
}*/
} }
return result; return result;
@ -361,4 +346,33 @@ namespace ZL {
return result; return result;
} }
VertexDataStruct StoneGroup::inflateOneDataOnly(int index, float scaleModifier)
{
static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337);
VertexDataStruct result;
for (const auto& inst : allInstances[index]) {
Matrix3f rotMat = inst.rotation.toRotationMatrix();
for (size_t j = 0; j < baseStone.PositionData.size(); ++j) {
Vector3f p = baseStone.PositionData[j];
Vector3f n = baseStone.NormalData[j];
p(0) *= inst.scale(0) * scaleModifier;
p(1) *= inst.scale(1) * scaleModifier;
p(2) *= inst.scale(2) * scaleModifier;
result.PositionData.push_back(rotMat * p + inst.position);
result.NormalData.push_back(rotMat * n);
result.TexCoordData.push_back(baseStone.TexCoordData[j]);
}
}
return result;
}
} // namespace ZL } // namespace ZL

View File

@ -22,6 +22,13 @@ namespace ZL {
Eigen::Quaternionf rotation; Eigen::Quaternionf rotation;
}; };
enum class ChunkStatus {
Empty, // Äàííûõ íåò
Generating, // Çàäà÷à â TaskManager (CPU)
ReadyToUpload, // Äàííûå â ïàìÿòè, æäóò î÷åðåäè â ãëàâíûé ïîòîê
Live // Çàãðóæåíî â GPU è ãîòîâî ê îòðèñîâêå
};
struct StoneGroup { struct StoneGroup {
// mesh.PositionData è ïðî÷èå áóäóò çàïîëíÿòüñÿ â inflate() // mesh.PositionData è ïðî÷èå áóäóò çàïîëíÿòüñÿ â inflate()
VertexDataStruct mesh; VertexDataStruct mesh;
@ -32,6 +39,14 @@ namespace ZL {
std::vector<VertexRenderStruct> inflate(int count); std::vector<VertexRenderStruct> inflate(int count);
VertexRenderStruct inflateOne(int index, float scaleModifier); VertexRenderStruct inflateOne(int index, float scaleModifier);
VertexDataStruct inflateOneDataOnly(int index, float scaleModifier);
std::vector<ChunkStatus> statuses;
// Èíèöèàëèçàöèÿ ñòàòóñîâ ïðè ñîçäàíèè ãðóïïû
void initStatuses() {
statuses.assign(allInstances.size(), ChunkStatus::Empty);
}
}; };
// Òåïåðü âîçâðàùàåò çàãîòîâêó ñî âñåìè ïàðàìåòðàìè, íî áåç òÿæåëîãî ìåøà // Òåïåðü âîçâðàùàåò çàãîòîâêó ñî âñåìè ïàðàìåòðàìè, íî áåç òÿæåëîãî ìåøà

View File

43
src/utils/TaskManager.h Normal file
View File

@ -0,0 +1,43 @@
#pragma once
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#include <memory>
namespace ZL {
class TaskManager {
private:
boost::asio::io_context ioContext;
std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> workGuard;
std::vector<std::thread> workers;
public:
//TaskManager(size_t threadCount = std::thread::hardware_concurrency()) {
TaskManager(size_t threadCount = 2) {
workGuard = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(ioContext.get_executor());
for (size_t i = 0; i < threadCount; ++i) {
workers.emplace_back([this]() {
ioContext.run();
});
}
}
// Ìåòîä äëÿ äîáàâëåíèÿ ôîíîâîé çàäà÷è
void EnqueueBackgroundTask(std::function<void()> task) {
boost::asio::post(ioContext, task);
}
// Graceful shutdown
~TaskManager() {
workGuard.reset(); // Ðàçðåøàåì ioContext.run() çàâåðøèòüñÿ, êîãäà çàäà÷ íå îñòàíåòñÿ
ioContext.stop(); // Îïöèîíàëüíî: íåìåäëåííàÿ îñòàíîâêà
for (auto& t : workers) {
if (t.joinable()) t.join();
}
}
};
} // namespace ZL