Multithreading (#1541)

* Create Worker class for multithreading

* Update TombEngine.vcxproj

* Rename GetWorkerCount() to GetThreadCount()

* Add ProcessInParallel template for vectors

* Add multiThreaded flag to settings, process sprites in parallel

* Update Flow.Settings.html

* Refine WorkerManager class conventions; deinit threads properly

* Don't require explicit destruction

* Address basic PR notes

* Update Worker.cpp

* Simplify ThreadManager class

* Add method for running single task

* Use singleton pattern; use more appropriate Controller suffix

* Update WorkerController template method

* Revise method

* Handle exception in ~WorkerController()

* Grammar

* Correctly init single-threaded mode

* Update CHANGELOG.md

* Defer thread init until g_GameFlow is valid

* unsigned int -> int

* Rename class

---------

Co-authored-by: Lwmte <3331699+Lwmte@users.noreply.github.com>
This commit is contained in:
Sezz 2025-02-12 18:59:54 +11:00 committed by GitHub
parent d37ac17a39
commit ca56f62f54
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 325 additions and 52 deletions

View file

@ -49,6 +49,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
* Fixed gravity being applied when vaulting on the same frame as the player lands. * Fixed gravity being applied when vaulting on the same frame as the player lands.
### New Features ### New Features
* Added multithreading setting to flow system settings.
* Added realtime shader reloading in debug mode by pressing F9 key. * Added realtime shader reloading in debug mode by pressing F9 key.
* Added load, save, stopwatch and compass as a functional pick-up items with ability to add or remove them from inventory. * Added load, save, stopwatch and compass as a functional pick-up items with ability to add or remove them from inventory.
* Increased particle limit from 1024 to 4096. * Increased particle limit from 1024 to 4096.

View file

@ -305,6 +305,10 @@
<td class="summary">How should the application respond to script errors?</td> <td class="summary">How should the application respond to script errors?</td>
</tr> </tr>
<tr> <tr>
<td class="name" ><a href="#multiThreaded">multiThreaded</a></td>
<td class="summary">Use multithreading in certain calculations.</td>
</tr>
<tr>
<td class="name" ><a href="#fastReload">fastReload</a></td> <td class="name" ><a href="#fastReload">fastReload</a></td>
<td class="summary">Can the game utilize the fast reload feature?</td> <td class="summary">Can the game utilize the fast reload feature?</td>
</tr> </tr>
@ -1253,6 +1257,29 @@
</dd>
<dt>
<a name = "multiThreaded"></a>
<strong>multiThreaded</strong>
</dt>
<dd>
Use multithreading in certain calculations. <br>
When set to <code>true</code>, some performance-critical calculations will be performed in parallel, which can give
a significant performance boost. Don't disable unless you have problems with launching or using TombEngine.
<ul>
<li><span class="parameter">multiThreaded</span>
<span class="types"><span class="type">bool</span></span>
determines whether to use multithreading or not. */
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "fastReload"></a> <a name = "fastReload"></a>

View file

@ -509,7 +509,7 @@ namespace TEN::Renderer
const Vector4& color0, const Vector4& color1, const Vector4& color2, const Vector4& color3, const Vector4& color0, const Vector4& color1, const Vector4& color2, const Vector4& color3,
BlendMode blendMode, RenderView& view, SpriteRenderType renderType = SpriteRenderType::Default); BlendMode blendMode, RenderView& view, SpriteRenderType renderType = SpriteRenderType::Default);
Matrix GetWorldMatrixForSprite(RendererSpriteToDraw* spr, RenderView& view); Matrix GetWorldMatrixForSprite(const RendererSpriteToDraw& sprite, RenderView& view);
RendererObject& GetRendererObject(GAME_OBJECT_ID id); RendererObject& GetRendererObject(GAME_OBJECT_ID id);
RendererMesh* GetMesh(int meshIndex); RendererMesh* GetMesh(int meshIndex);
Texture2D CreateDefaultNormalTexture(); Texture2D CreateDefaultNormalTexture();

View file

@ -2925,7 +2925,7 @@ namespace TEN::Renderer
rDrawSprite.Width = STAR_SIZE * star.Scale; rDrawSprite.Width = STAR_SIZE * star.Scale;
rDrawSprite.Height = STAR_SIZE * star.Scale; rDrawSprite.Height = STAR_SIZE * star.Scale;
_stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(rDrawSprite, renderView);
_stInstancedSpriteBuffer.Sprites[i].Color = Vector4( _stInstancedSpriteBuffer.Sprites[i].Color = Vector4(
star.Color.x, star.Color.x,
star.Color.y, star.Color.y,
@ -2988,7 +2988,7 @@ namespace TEN::Renderer
rDrawSprite.Height = 192; rDrawSprite.Height = 192;
rDrawSprite.ConstrainAxis = meteor.Direction; rDrawSprite.ConstrainAxis = meteor.Direction;
_stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(rDrawSprite, renderView);
_stInstancedSpriteBuffer.Sprites[i].Color = Vector4( _stInstancedSpriteBuffer.Sprites[i].Color = Vector4(
meteor.Color.x, meteor.Color.x,
meteor.Color.y, meteor.Color.y,
@ -3092,7 +3092,7 @@ namespace TEN::Renderer
rDrawSprite.Height = SUN_SIZE; rDrawSprite.Height = SUN_SIZE;
rDrawSprite.color = renderView.LensFlaresToDraw[0].Color; rDrawSprite.color = renderView.LensFlaresToDraw[0].Color;
_stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(&rDrawSprite, renderView); _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(rDrawSprite, renderView);
_stInstancedSpriteBuffer.Sprites[0].Color = renderView.LensFlaresToDraw[0].Color; _stInstancedSpriteBuffer.Sprites[0].Color = renderView.LensFlaresToDraw[0].Color;
_stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1; _stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1;
_stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = 0; _stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = 0;
@ -3442,7 +3442,7 @@ namespace TEN::Renderer
uv2 = spr->Sprite->UV[2]; uv2 = spr->Sprite->UV[2];
uv3 = spr->Sprite->UV[3]; uv3 = spr->Sprite->UV[3];
auto world = GetWorldMatrixForSprite(currentObject->Sprite, view); auto world = GetWorldMatrixForSprite(*currentObject->Sprite, view);
Vertex v0; Vertex v0;
v0.Position = Vector3::Transform(p0t, world); v0.Position = Vector3::Transform(p0t, world);

View file

@ -1291,14 +1291,14 @@ namespace TEN::Renderer
} }
} }
Matrix Renderer::GetWorldMatrixForSprite(RendererSpriteToDraw* sprite, RenderView& view) Matrix Renderer::GetWorldMatrixForSprite(const RendererSpriteToDraw& sprite, RenderView& view)
{ {
auto spriteMatrix = Matrix::Identity; auto spriteMatrix = Matrix::Identity;
auto scaleMatrix = Matrix::CreateScale(sprite->Width * sprite->Scale, sprite->Height * sprite->Scale, sprite->Scale); auto scaleMatrix = Matrix::CreateScale(sprite.Width * sprite.Scale, sprite.Height * sprite.Scale, sprite.Scale);
auto spritePos = sprite->pos; auto spritePos = sprite.pos;
if (sprite->Type == SpriteType::ThreeD) if (sprite.Type == SpriteType::ThreeD)
{ {
ReflectMatrixOptionally(spriteMatrix); ReflectMatrixOptionally(spriteMatrix);
} }
@ -1307,23 +1307,23 @@ namespace TEN::Renderer
ReflectVectorOptionally(spritePos); ReflectVectorOptionally(spritePos);
} }
switch (sprite->Type) switch (sprite.Type)
{ {
case SpriteType::Billboard: case SpriteType::Billboard:
{ {
auto cameraUp = Vector3(view.Camera.View._12, view.Camera.View._22, view.Camera.View._32); auto cameraUp = Vector3(view.Camera.View._12, view.Camera.View._22, view.Camera.View._32);
spriteMatrix = scaleMatrix * Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateBillboard(spritePos, Camera.pos.ToVector3(), cameraUp); spriteMatrix = scaleMatrix * Matrix::CreateRotationZ(sprite.Rotation) * Matrix::CreateBillboard(spritePos, Camera.pos.ToVector3(), cameraUp);
} }
break; break;
case SpriteType::CustomBillboard: case SpriteType::CustomBillboard:
{ {
auto rotMatrix = Matrix::CreateRotationY(sprite->Rotation); auto rotMatrix = Matrix::CreateRotationY(sprite.Rotation);
auto quadForward = Vector3(0.0f, 0.0f, 1.0f); auto quadForward = Vector3(0.0f, 0.0f, 1.0f);
spriteMatrix = scaleMatrix * Matrix::CreateConstrainedBillboard( spriteMatrix = scaleMatrix * Matrix::CreateConstrainedBillboard(
spritePos, spritePos,
Camera.pos.ToVector3(), Camera.pos.ToVector3(),
sprite->ConstrainAxis, sprite.ConstrainAxis,
nullptr, nullptr,
&quadForward); &quadForward);
} }
@ -1332,7 +1332,7 @@ namespace TEN::Renderer
case SpriteType::LookAtBillboard: case SpriteType::LookAtBillboard:
{ {
auto translationMatrix = Matrix::CreateTranslation(spritePos); auto translationMatrix = Matrix::CreateTranslation(spritePos);
auto rotMatrix = Matrix::CreateRotationZ(sprite->Rotation) * Matrix::CreateLookAt(Vector3::Zero, sprite->LookAtAxis, Vector3::UnitZ); auto rotMatrix = Matrix::CreateRotationZ(sprite.Rotation) * Matrix::CreateLookAt(Vector3::Zero, sprite.LookAtAxis, Vector3::UnitZ);
spriteMatrix = scaleMatrix * rotMatrix * translationMatrix; spriteMatrix = scaleMatrix * rotMatrix * translationMatrix;
} }
break; break;

View file

@ -1,14 +1,16 @@
#include "framework.h" #include "framework.h"
#include "Renderer/Structures/RendererSprite.h" #include "Renderer/Structures/RendererSprite.h"
#include "Renderer/Structures/RendererSpriteBucket.h" #include "Renderer/Structures/RendererSpriteBucket.h"
#include "Renderer/Renderer.h" #include "Renderer/Renderer.h"
#include "Specific/Parallel.h"
using namespace TEN::Renderer::Structures;
namespace TEN::Renderer namespace TEN::Renderer
{ {
using namespace TEN::Renderer::Structures;
void Renderer::AddSpriteBillboard(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, float scale, void Renderer::AddSpriteBillboard(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, float scale,
Vector2 size, BlendMode blendMode, bool isSoftParticle, RenderView& view, SpriteRenderType renderType) Vector2 size, BlendMode blendMode, bool isSoftParticle, RenderView& view, SpriteRenderType renderType)
{ {
if (scale <= 0.0f) if (scale <= 0.0f)
scale = 1.0f; scale = 1.0f;
@ -38,8 +40,8 @@ namespace TEN::Renderer
} }
void Renderer::AddSpriteBillboardConstrained(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D, void Renderer::AddSpriteBillboardConstrained(RendererSprite* sprite, const Vector3& pos, const Vector4& color, float orient2D,
float scale, Vector2 size, BlendMode blendMode, const Vector3& constrainAxis, float scale, Vector2 size, BlendMode blendMode, const Vector3& constrainAxis,
bool isSoftParticle, RenderView& view, SpriteRenderType renderType) bool isSoftParticle, RenderView& view, SpriteRenderType renderType)
{ {
if (scale <= 0.0f) if (scale <= 0.0f)
scale = 1.0f; scale = 1.0f;
@ -261,16 +263,16 @@ namespace TEN::Renderer
return; return;
// Draw instanced sprites. // Draw instanced sprites.
bool wasGPUSet = false; bool wasGpuSet = false;
for (auto& spriteBucket : _spriteBuckets) for (const auto& spriteBucket : _spriteBuckets)
{ {
if (spriteBucket.SpritesToDraw.size() == 0 || !spriteBucket.IsBillboard) if (spriteBucket.SpritesToDraw.empty() || !spriteBucket.IsBillboard)
continue; continue;
if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0)) if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0))
continue; continue;
if (!wasGPUSet) if (!wasGpuSet)
{ {
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
@ -286,42 +288,45 @@ namespace TEN::Renderer
unsigned int offset = 0; unsigned int offset = 0;
_context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset);
wasGPUSet = true; wasGpuSet = true;
} }
// Prepare constant buffer for instanced sprites. // Define sprite preparation logic.
for (int i = 0; i < spriteBucket.SpritesToDraw.size(); i++) auto prepareSprites = [&](int start, int end)
{ {
auto& rDrawSprite = spriteBucket.SpritesToDraw[i]; for (int i = start; i < end; i++)
{
const auto& spriteToDraw = spriteBucket.SpritesToDraw[i];
_stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(&rDrawSprite, view); _stInstancedSpriteBuffer.Sprites[i].World = GetWorldMatrixForSprite(spriteToDraw, view);
_stInstancedSpriteBuffer.Sprites[i].Color = rDrawSprite.color; _stInstancedSpriteBuffer.Sprites[i].Color = spriteToDraw.color;
_stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1; _stInstancedSpriteBuffer.Sprites[i].IsBillboard = 1.0f;
_stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = rDrawSprite.SoftParticle ? 1 : 0; _stInstancedSpriteBuffer.Sprites[i].IsSoftParticle = spriteToDraw.SoftParticle ? 1.0f : 0.0f;
// NOTE: Strange packing due to particular HLSL 16 byte alignment requirements. // NOTE: Strange packing due to particular HLSL 16 byte alignment requirements.
_stInstancedSpriteBuffer.Sprites[i].UV[0].x = rDrawSprite.Sprite->UV[0].x; _stInstancedSpriteBuffer.Sprites[i].UV[0].x = spriteToDraw.Sprite->UV[0].x;
_stInstancedSpriteBuffer.Sprites[i].UV[0].y = rDrawSprite.Sprite->UV[1].x; _stInstancedSpriteBuffer.Sprites[i].UV[0].y = spriteToDraw.Sprite->UV[1].x;
_stInstancedSpriteBuffer.Sprites[i].UV[0].z = rDrawSprite.Sprite->UV[2].x; _stInstancedSpriteBuffer.Sprites[i].UV[0].z = spriteToDraw.Sprite->UV[2].x;
_stInstancedSpriteBuffer.Sprites[i].UV[0].w = rDrawSprite.Sprite->UV[3].x; _stInstancedSpriteBuffer.Sprites[i].UV[0].w = spriteToDraw.Sprite->UV[3].x;
_stInstancedSpriteBuffer.Sprites[i].UV[1].x = rDrawSprite.Sprite->UV[0].y; _stInstancedSpriteBuffer.Sprites[i].UV[1].x = spriteToDraw.Sprite->UV[0].y;
_stInstancedSpriteBuffer.Sprites[i].UV[1].y = rDrawSprite.Sprite->UV[1].y; _stInstancedSpriteBuffer.Sprites[i].UV[1].y = spriteToDraw.Sprite->UV[1].y;
_stInstancedSpriteBuffer.Sprites[i].UV[1].z = rDrawSprite.Sprite->UV[2].y; _stInstancedSpriteBuffer.Sprites[i].UV[1].z = spriteToDraw.Sprite->UV[2].y;
_stInstancedSpriteBuffer.Sprites[i].UV[1].w = rDrawSprite.Sprite->UV[3].y; _stInstancedSpriteBuffer.Sprites[i].UV[1].w = spriteToDraw.Sprite->UV[3].y;
} }
};
g_Parallel.AddTasks((int)spriteBucket.SpritesToDraw.size(), prepareSprites).wait();
BindTexture(TextureRegister::ColorMap, spriteBucket.Sprite->Texture, SamplerStateRegister::LinearClamp); BindTexture(TextureRegister::ColorMap, spriteBucket.Sprite->Texture, SamplerStateRegister::LinearClamp);
_cbInstancedSpriteBuffer.UpdateData(_stInstancedSpriteBuffer, _context.Get()); _cbInstancedSpriteBuffer.UpdateData(_stInstancedSpriteBuffer, _context.Get());
// Draw sprites with instancing. // Draw sprites with instancing.
DrawInstancedTriangles(4, (unsigned int)spriteBucket.SpritesToDraw.size(), 0); DrawInstancedTriangles(4, (int)spriteBucket.SpritesToDraw.size(), 0);
_numInstancedSpritesDrawCalls++; _numInstancedSpritesDrawCalls++;
} }
// Draw 3D non-instanced sprites. // Draw 3D non-instanced sprites.
wasGPUSet = false; wasGpuSet = false;
for (auto& spriteBucket : _spriteBuckets) for (auto& spriteBucket : _spriteBuckets)
{ {
@ -331,7 +336,7 @@ namespace TEN::Renderer
if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0)) if (!SetupBlendModeAndAlphaTest(spriteBucket.BlendMode, rendererPass, 0))
continue; continue;
if (!wasGPUSet) if (!wasGpuSet)
{ {
_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); _context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
@ -342,10 +347,10 @@ namespace TEN::Renderer
_shaders.Bind(Shader::Sprites); _shaders.Bind(Shader::Sprites);
wasGPUSet = true; wasGpuSet = true;
} }
_stSprite.IsSoftParticle = spriteBucket.IsSoftParticle ? 1 : 0; _stSprite.IsSoftParticle = spriteBucket.IsSoftParticle ? 1.0f : 0.0f;
_stSprite.RenderType = (int)spriteBucket.RenderType; _stSprite.RenderType = (int)spriteBucket.RenderType;
_cbSprite.UpdateData(_stSprite, _context.Get()); _cbSprite.UpdateData(_stSprite, _context.Get());
@ -414,11 +419,11 @@ namespace TEN::Renderer
_shaders.Bind(Shader::InstancedSprites); _shaders.Bind(Shader::InstancedSprites);
// Set up vertex buffer and parameters. // Set up vertex buffer and parameters.
UINT stride = sizeof(Vertex); unsigned int stride = sizeof(Vertex);
UINT offset = 0; unsigned int offset = 0;
_context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset); _context->IASetVertexBuffers(0, 1, _quadVertexBuffer.Buffer.GetAddressOf(), &stride, &offset);
_stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(object->Sprite, view); _stInstancedSpriteBuffer.Sprites[0].World = GetWorldMatrixForSprite(*object->Sprite, view);
_stInstancedSpriteBuffer.Sprites[0].Color = object->Sprite->color; _stInstancedSpriteBuffer.Sprites[0].Color = object->Sprite->color;
_stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1; _stInstancedSpriteBuffer.Sprites[0].IsBillboard = 1;
_stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = object->Sprite->SoftParticle ? 1 : 0; _stInstancedSpriteBuffer.Sprites[0].IsSoftParticle = object->Sprite->SoftParticle ? 1 : 0;

View file

@ -328,6 +328,12 @@ namespace TEN::Scripting
// @tfield Flow.ErrorMode errorMode error mode to use. */ // @tfield Flow.ErrorMode errorMode error mode to use. */
"errorMode", &SystemSettings::ErrorMode, "errorMode", &SystemSettings::ErrorMode,
/// Use multithreading in certain calculations. <br>
// When set to `true`, some performance-critical calculations will be performed in parallel, which can give
// a significant performance boost. Don't disable unless you have problems with launching or using TombEngine.
// @tfield bool multithreaded determines whether to use multithreading or not. */
"multithreaded", &SystemSettings::Multithreaded,
/// Can the game utilize the fast reload feature? <br> /// Can the game utilize the fast reload feature? <br>
// When set to `true`, the game will attempt to perform fast savegame reloading if current level is the same as // When set to `true`, the game will attempt to perform fast savegame reloading if current level is the same as
// the level loaded from the savegame. It will not work if the level timestamp or checksum has changed // the level loaded from the savegame. It will not work if the level timestamp or checksum has changed

View file

@ -78,8 +78,9 @@ namespace TEN::Scripting
struct SystemSettings struct SystemSettings
{ {
ErrorMode ErrorMode = ErrorMode::Warn; ErrorMode ErrorMode = ErrorMode::Warn;
bool FastReload = true; bool FastReload = true;
bool Multithreaded = true;
static void Register(sol::table& parent); static void Register(sol::table& parent);
}; };

View file

@ -0,0 +1,173 @@
#include "framework.h"
#include "Specific/Parallel.h"
#include "Scripting/Include/Flow/ScriptInterfaceFlowHandler.h"
namespace TEN::Utils
{
ParallelTaskManager& g_Parallel = ParallelTaskManager::Get();
ParallelTaskManager::ParallelTaskManager()
{
_deinitialize = false;
}
ParallelTaskManager::~ParallelTaskManager()
{
// LOCK: Restrict shutdown flag access.
{
auto taskLock = std::lock_guard(_taskMutex);
_deinitialize = true;
}
// Notify all threads they should stop.
_taskCond.notify_all();
// Join all threads.
for (auto& thread : _threads)
{
if (!thread.joinable())
continue;
try
{
thread.join();
}
catch (const std::exception& ex)
{
TENLog("Error joining thread: " + std::string(ex.what()), LogLevel::Error);
}
}
}
ParallelTaskManager& ParallelTaskManager::Get()
{
static auto instance = ParallelTaskManager();
return instance;
}
unsigned int ParallelTaskManager::GetThreadCount() const
{
return (unsigned int)_threads.size();
}
unsigned int ParallelTaskManager::GetCoreCount() const
{
return std::max(std::thread::hardware_concurrency(), 1u);
}
void ParallelTaskManager::Initialize()
{
// Reserve threads.
unsigned int threadCount = g_GameFlow->GetSettings()->System.Multithreaded ? (GetCoreCount() * 2) : 1;
_threads.reserve(threadCount);
// Create threads.
for (int i = 0; i < threadCount; i++)
_threads.push_back(std::thread(&ParallelTaskManager::Worker, this));
}
std::future<void> ParallelTaskManager::AddTask(const ParallelTask& task)
{
return AddTasks(ParallelTaskGroup{ task });
}
std::future<void> ParallelTaskManager::AddTasks(const ParallelTaskGroup& tasks)
{
// HEAP ALLOC: Create counter and promise.
auto counter = std::make_shared<std::atomic<int>>();
auto promise = std::make_shared<std::promise<void>>();
counter->store((int)tasks.size(), std::memory_order_release);
// Add group tasks.
for (const auto& task : tasks)
AddTask(task, counter, promise);
// Notify available threads to handle tasks.
_taskCond.notify_all();
// Return future to wait on task group completion if needed.
return promise->get_future();
}
std::future<void> ParallelTaskManager::AddTasks(int elementCount, const std::function<void(int, int)>& splitTask)
{
// TODO: Make this a configuration option?
constexpr auto SERIAL_UNIT_COUNT_MAX = 32;
auto tasks = ParallelTaskGroup{};
// Process in parallel.
if (g_GameFlow->GetSettings()->System.Multithreaded &&
elementCount > SERIAL_UNIT_COUNT_MAX)
{
int threadCount = GetCoreCount();
int chunkSize = ((elementCount + threadCount) - 1) / threadCount;
// Collect group tasks.
tasks.reserve(threadCount);
for (int i = 0; i < threadCount; i++)
{
int start = i * chunkSize;
int end = std::min(start + chunkSize, elementCount);
tasks.push_back([&splitTask, start, end]() { splitTask(start, end); });
}
}
// Process linearly.
else
{
tasks.push_back([&splitTask, elementCount]() { splitTask(0, elementCount); });
}
// Add task group and return future to wait on completion if needed.
return AddTasks(tasks);
}
void ParallelTaskManager::Worker()
{
while (true)
{
auto task = ParallelTask();
// LOCK: Restrict task queue access.
{
auto taskLock = std::unique_lock(_taskMutex);
_taskCond.wait(taskLock, [this] { return (_deinitialize || !_tasks.empty()); });
// Shutting down and no pending tasks; return early.
if (_deinitialize && _tasks.empty())
return;
// Get task.
task = _tasks.front();
_tasks.pop();
}
// Execute task.
if (task)
task();
}
}
void ParallelTaskManager::AddTask(const ParallelTask& task, std::shared_ptr<std::atomic<int>> counter, std::shared_ptr<std::promise<void>> promise)
{
// Increment counter for task group.
counter->fetch_add(1, std::memory_order_relaxed);
// Add task with promise and counter handling.
_tasks.push([this, task, counter, promise]() { HandleTask(task, *counter, *promise); });
}
void ParallelTaskManager::HandleTask(const ParallelTask& task, std::atomic<int>& counter, std::promise<void>& promise)
{
// Execute task.
if (task)
task();
// Check for task group completion.
if (counter.fetch_sub(1, std::memory_order_acq_rel) == 1)
promise.set_value();
}
}

View file

@ -0,0 +1,54 @@
#pragma once
namespace TEN::Utils
{
using ParallelTask = std::function<void()>;
using ParallelTaskGroup = std::vector<ParallelTask>;
class ParallelTaskManager
{
private:
// Fields
std::vector<std::thread> _threads = {};
std::queue<ParallelTask> _tasks = {};
std::mutex _taskMutex = {};
std::condition_variable _taskCond = {};
bool _deinitialize = false;
// Constructors, destructors
ParallelTaskManager();
ParallelTaskManager(const ParallelTaskManager& manager) = delete;
~ParallelTaskManager();
public:
// Getters
static ParallelTaskManager& Get();
unsigned int GetThreadCount() const;
unsigned int GetCoreCount() const;
// Utilities
void Initialize();
std::future<void> AddTask(const ParallelTask& task);
std::future<void> AddTasks(const ParallelTaskGroup& tasks);
std::future<void> AddTasks(int elementCount, const std::function<void(int, int)>& splitTask);
private:
// Helpers
void Worker();
void AddTask(const ParallelTask& task, std::shared_ptr<std::atomic<int>> counter, std::shared_ptr<std::promise<void>> promise);
void HandleTask(const ParallelTask& task, std::atomic<int>& counter, std::promise<void>& promise);
// Operators
ParallelTaskManager& operator =(const ParallelTaskManager& manager) = delete;
};
extern ParallelTaskManager& g_Parallel;
}

View file

@ -14,6 +14,7 @@
#include "Sound/sound.h" #include "Sound/sound.h"
#include "Specific/level.h" #include "Specific/level.h"
#include "Specific/configuration.h" #include "Specific/configuration.h"
#include "Specific/Parallel.h"
#include "Specific/trutils.h" #include "Specific/trutils.h"
#include "Scripting/Internal/LanguageScript.h" #include "Scripting/Internal/LanguageScript.h"
#include "Scripting/Include/ScriptInterfaceState.h" #include "Scripting/Include/ScriptInterfaceState.h"
@ -592,6 +593,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
DoTheGame = true; DoTheGame = true;
g_Parallel.Initialize();
ThreadEnded = false; ThreadEnded = false;
ThreadHandle = BeginThread(GameMain, ThreadID); ThreadHandle = BeginThread(GameMain, ThreadID);

View file

@ -954,6 +954,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.
<ClInclude Include="Specific\Input\Input.h" /> <ClInclude Include="Specific\Input\Input.h" />
<ClInclude Include="Specific\Input\InputAction.h" /> <ClInclude Include="Specific\Input\InputAction.h" />
<ClInclude Include="Specific\LevelCameraInfo.h" /> <ClInclude Include="Specific\LevelCameraInfo.h" />
<ClInclude Include="Specific\Parallel.h" />
<ClInclude Include="Specific\RGBAColor8Byte.h" /> <ClInclude Include="Specific\RGBAColor8Byte.h" />
<ClInclude Include="Specific\clock.h" /> <ClInclude Include="Specific\clock.h" />
<ClInclude Include="Specific\configuration.h" /> <ClInclude Include="Specific\configuration.h" />
@ -1375,6 +1376,7 @@ if not exist "%ScriptsDir%\Strings.lua" xcopy /Y "$(SolutionDir)Scripts\Strings.
<ClCompile Include="Specific\IO\ChunkReader.cpp" /> <ClCompile Include="Specific\IO\ChunkReader.cpp" />
<ClCompile Include="Specific\IO\Streams.cpp" /> <ClCompile Include="Specific\IO\Streams.cpp" />
<ClCompile Include="Specific\level.cpp" /> <ClCompile Include="Specific\level.cpp" />
<ClCompile Include="Specific\Parallel.cpp" />
<ClCompile Include="Specific\RGBAColor8Byte.cpp" /> <ClCompile Include="Specific\RGBAColor8Byte.cpp" />
<ClCompile Include="Specific\trutils.cpp" /> <ClCompile Include="Specific\trutils.cpp" />
<ClCompile Include="Specific\winmain.cpp" /> <ClCompile Include="Specific\winmain.cpp" />

View file

@ -2,6 +2,7 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <atomic>
#include <d3d11.h> #include <d3d11.h>
#include <d3dcompiler.h> #include <d3dcompiler.h>
#include <deque> #include <deque>
@ -20,6 +21,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string> #include <string>
#include <queue>
#include <vector> #include <vector>
using namespace DirectX; using namespace DirectX;