From 6b009e6a1e7bc2d3dc7a870dcc3e489199053b42 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sat, 18 Nov 2023 23:37:32 +0000 Subject: [PATCH 01/17] Added BS Threadpool --- Managers/ThreadMan.cpp | 9 +- Managers/ThreadMan.h | 11 +- RTEA.vcxproj | 22 +- RTEA.vcxproj.filters | 6 + System/StandardIncludes.h | 1 + .../common/thread-pool-3.5.0/.clang-format | 190 ++ .../.github/ISSUE_TEMPLATE/bug_report.md | 37 + .../.github/ISSUE_TEMPLATE/failed-tests.md | 26 + .../.github/ISSUE_TEMPLATE/feature_request.md | 23 + .../.github/pull_request_template.md | 26 + .../common/thread-pool-3.5.0/.gitignore | 1 + .../common/thread-pool-3.5.0/CHANGELOG.md | 262 +++ .../common/thread-pool-3.5.0/CITATION.bib | 13 + .../common/thread-pool-3.5.0/CITATION.cff | 24 + .../common/thread-pool-3.5.0/LICENSE.txt | 21 + .../common/thread-pool-3.5.0/README.md | 1353 +++++++++++++++ .../include/BS_thread_pool.hpp | 819 +++++++++ .../include/BS_thread_pool_light.hpp | 327 ++++ .../tests/BS_thread_pool_test.cpp | 1535 +++++++++++++++++ .../tests/BS_thread_pool_test.ps1 | 224 +++ 20 files changed, 4907 insertions(+), 23 deletions(-) create mode 100644 external/include/common/thread-pool-3.5.0/.clang-format create mode 100644 external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/failed-tests.md create mode 100644 external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/feature_request.md create mode 100644 external/include/common/thread-pool-3.5.0/.github/pull_request_template.md create mode 100644 external/include/common/thread-pool-3.5.0/.gitignore create mode 100644 external/include/common/thread-pool-3.5.0/CHANGELOG.md create mode 100644 external/include/common/thread-pool-3.5.0/CITATION.bib create mode 100644 external/include/common/thread-pool-3.5.0/CITATION.cff create mode 100644 external/include/common/thread-pool-3.5.0/LICENSE.txt create mode 100644 external/include/common/thread-pool-3.5.0/README.md create mode 100644 external/include/common/thread-pool-3.5.0/include/BS_thread_pool.hpp create mode 100644 external/include/common/thread-pool-3.5.0/include/BS_thread_pool_light.hpp create mode 100644 external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.cpp create mode 100644 external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.ps1 diff --git a/Managers/ThreadMan.cpp b/Managers/ThreadMan.cpp index 73984c924..579e8de5f 100644 --- a/Managers/ThreadMan.cpp +++ b/Managers/ThreadMan.cpp @@ -19,11 +19,6 @@ using namespace std; namespace RTE { -#define DELTABUFFERSIZE 30 - -const string ThreadMan::m_ClassName = "ThreadMan"; - - ////////////////////////////////////////////////////////////////////////////////////////// // Method: Clear ////////////////////////////////////////////////////////////////////////////////////////// @@ -32,6 +27,7 @@ const string ThreadMan::m_ClassName = "ThreadMan"; void ThreadMan::Clear() { + m_ThreadPool.reset(); } @@ -42,7 +38,6 @@ void ThreadMan::Clear() int ThreadMan::Create() { - return 0; } @@ -54,8 +49,6 @@ int ThreadMan::Create() void ThreadMan::Destroy() { - - Clear(); } diff --git a/Managers/ThreadMan.h b/Managers/ThreadMan.h index 3b5599c65..0840e8fab 100644 --- a/Managers/ThreadMan.h +++ b/Managers/ThreadMan.h @@ -14,14 +14,11 @@ ////////////////////////////////////////////////////////////////////////////////////////// // Inclusions of header files -#include "global_types.h" - - -//#include - #include "Singleton.h" #define g_ThreadMan ThreadMan::Instance() +#include "BS_thread_pool.hpp" + namespace RTE { @@ -96,6 +93,9 @@ class ThreadMan: void Destroy(); + BS::thread_pool& GetThreadPool() { return m_ThreadPool; } + + ////////////////////////////////////////////////////////////////////////////////////////// // Virtual method: GetClassName ////////////////////////////////////////////////////////////////////////////////////////// @@ -134,6 +134,7 @@ class ThreadMan: ThreadMan(const ThreadMan &reference); ThreadMan & operator=(const ThreadMan &rhs); + BS::thread_pool m_ThreadPool; }; } // namespace RTE diff --git a/RTEA.vcxproj b/RTEA.vcxproj index cabfd3f73..f30777c7a 100644 --- a/RTEA.vcxproj +++ b/RTEA.vcxproj @@ -160,7 +160,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) Disabled - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;DEBUG_BUILD;DEBUGMODE;TARGET_MACHINE_X86;%(PreprocessorDefinitions) false EnableFastChecks @@ -212,7 +212,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) MaxSpeed - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;TRACY_ENABLE;TRACY_ON_DEMAND;DEBUG_BUILD;DEBUGMODE;%(PreprocessorDefinitions) false EnableFastChecks @@ -264,7 +264,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) Disabled - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;MIN_DEBUG_BUILD;DEBUGMODE;TARGET_MACHINE_X86;%(PreprocessorDefinitions) false EnableFastChecks @@ -316,7 +316,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) Disabled - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;MIN_DEBUG_BUILD;DEBUGMODE;%(PreprocessorDefinitions) false EnableFastChecks @@ -371,7 +371,7 @@ true Speed false - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;DEBUG_RELEASE_BUILD;NDEBUG;TRACY_ENABLE;TRACY_ON_DEMAND;TARGET_MACHINE_X86;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -432,7 +432,7 @@ true Speed false - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;TRACY_ENABLE;TRACY_ON_DEMAND;PROFILING_BUILD;NDEBUG;LUABIND_NO_ERROR_CHECKING;TARGET_MACHINE_X86;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -493,7 +493,7 @@ true Speed false - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;DEBUG_RELEASE_BUILD;NDEBUG;TRACY_ENABLE;TRACY_ON_DEMAND;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -554,7 +554,7 @@ true Speed true - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;TRACY_ENABLE;TRACY_ON_DEMAND;PROFILING_BUILD;NDEBUG;LUABIND_NO_ERROR_CHECKING;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -615,7 +615,7 @@ true Speed true - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;RELEASE_BUILD;NDEBUG;LUABIND_NO_ERROR_CHECKING;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_ONLY_LOCALHOST;TRACY_NO_BROADCAST;TARGET_MACHINE_X86;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -674,7 +674,7 @@ true Speed true - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;RELEASE_BUILD;NDEBUG;LUABIND_NO_ERROR_CHECKING;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_ONLY_LOCALHOST;TRACY_NO_BROADCAST;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -755,6 +755,7 @@ + @@ -998,6 +999,7 @@ + diff --git a/RTEA.vcxproj.filters b/RTEA.vcxproj.filters index 99d81cb88..ba0737859 100644 --- a/RTEA.vcxproj.filters +++ b/RTEA.vcxproj.filters @@ -621,6 +621,9 @@ System + + Managers + @@ -1194,6 +1197,9 @@ System + + Managers + diff --git a/System/StandardIncludes.h b/System/StandardIncludes.h index a83770356..e31b8f8a2 100644 --- a/System/StandardIncludes.h +++ b/System/StandardIncludes.h @@ -85,6 +85,7 @@ #include #include #include +#include namespace std { diff --git a/external/include/common/thread-pool-3.5.0/.clang-format b/external/include/common/thread-pool-3.5.0/.clang-format new file mode 100644 index 000000000..9d2d4ee02 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/.clang-format @@ -0,0 +1,190 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Allman +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 10000 +CommentPragmas: "^ IWYU pragma:" +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: "" +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "(Test)?$" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +--- diff --git a/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/bug_report.md b/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..1f02ed368 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Found a bug? Report it here. +title: "[BUG]" +labels: bug +assignees: bshoshany + +--- + +**Describe the bug** + +A clear and concise description of what the bug is. + +**Minimal working example** + +A short but complete program that can be compiled to reproduce the error. Paste the program between the two code fences. If it's too long or requires multiple files, attach the file(s) instead. + +```cpp +``` + +**Behavior** + +What behavior did you expect to get? What actually happened? If the code failed to compile, please include the full output of the compiler. + +**System information** + +* CPU model, architecture, # of cores and threads: +* Operating system: +* Name and version of C++ compiler: +* Full command used for compiling, including all compiler flags: +* Thread pool library version: + +(Please note that only the latest version of the thread pool library is supported.) + +**Additional information** + +Include any additional information here. diff --git a/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/failed-tests.md b/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/failed-tests.md new file mode 100644 index 000000000..1d20f23f7 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/failed-tests.md @@ -0,0 +1,26 @@ +--- +name: Failed tests +about: The provided automated tests failed on your system? Report it here. +title: "[TEST]" +labels: bug +assignees: bshoshany + +--- + +**System information** + +* CPU model, architecture, # of cores and threads: +* Operating system: +* Name and version of C++ compiler: +* Full command used for compiling, including all compiler flags: +* Thread pool library version: + +(Please note that only the latest version of the thread pool library is supported.) + +**Log file** + +Please attach the log file generated by the automated test program to this issue. + +**Additional information** + +Include any additional information here. diff --git a/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/feature_request.md b/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..7137cea24 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Want a new feature? Suggest it here. +title: "[REQ]" +labels: enhancement +assignees: bshoshany + +--- + +**Describe the new feature** + +A clear and concise description of the feature you want. + +**Code example** + +An example of code that utilizes the suggested feature. Paste or write it between the two code fences. + +```cpp +``` + +**Additional information** + +Include any additional information here. diff --git a/external/include/common/thread-pool-3.5.0/.github/pull_request_template.md b/external/include/common/thread-pool-3.5.0/.github/pull_request_template.md new file mode 100644 index 000000000..bcbacbf99 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/.github/pull_request_template.md @@ -0,0 +1,26 @@ +**Pull request policy (please read)** + +> Contributions are always welcome. However, I release my projects in cumulative updates after editing and testing them locally on my system, so my policy is not to accept any pull requests. If you open a pull request, and I decide to incorporate your suggestion into the project, I will first modify your code to comply with the project's coding conventions (formatting, syntax, naming, comments, programming practices, etc.), and perform some tests to ensure that the change doesn't break anything. I will then merge it into the next release of the project, possibly together with some other changes. The new release will also include a note in `CHANGELOG.md` with a link to your pull request, and modifications to the documentation in `README.md` as needed. + +**Describe the changes** + +What does your pull request fix or add to the library? + +**Style** + +Have you formatted your code using the `.clang-format` file attached to this project? + +**Testing** + +Have you tested the new code using the provided automated test program `BS_thread_pool_test.cpp` (preferably with the provided multi-compiler test script `BS_thread_pool_test.ps1`) and/or performed any other tests to ensure that the new code works correctly? + +If so, please provide information about the test system(s): + +* CPU model, architecture, # of cores and threads: +* Operating system: +* Name and version of C++ compiler: +* Full command used for compiling, including all compiler flags: + +**Additional information** + +Include any additional information here. diff --git a/external/include/common/thread-pool-3.5.0/.gitignore b/external/include/common/thread-pool-3.5.0/.gitignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/.gitignore @@ -0,0 +1 @@ +build diff --git a/external/include/common/thread-pool-3.5.0/CHANGELOG.md b/external/include/common/thread-pool-3.5.0/CHANGELOG.md new file mode 100644 index 000000000..bfa2cdf29 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/CHANGELOG.md @@ -0,0 +1,262 @@ +# `BS::thread_pool`: a fast, lightweight, and easy-to-use C++17 thread pool library + +By Barak Shoshany\ +Email: \ +Website: \ +GitHub: + +* [Version history](#version-history) + * [v3.5.0 (2023-05-25)](#v350-2023-05-25) + * [v3.4.0 (2023-05-12)](#v340-2023-05-12) + * [v3.3.0 (2022-08-03)](#v330-2022-08-03) + * [v3.2.0 (2022-07-28)](#v320-2022-07-28) + * [v3.1.0 (2022-07-13)](#v310-2022-07-13) + * [v3.0.0 (2022-05-30)](#v300-2022-05-30) + * [v2.0.0 (2021-08-14)](#v200-2021-08-14) + * [v1.9 (2021-07-29)](#v19-2021-07-29) + * [v1.8 (2021-07-28)](#v18-2021-07-28) + * [v1.7 (2021-06-02)](#v17-2021-06-02) + * [v1.6 (2021-05-26)](#v16-2021-05-26) + * [v1.5 (2021-05-07)](#v15-2021-05-07) + * [v1.4 (2021-05-05)](#v14-2021-05-05) + * [v1.3 (2021-05-03)](#v13-2021-05-03) + * [v1.2 (2021-04-29)](#v12-2021-04-29) + * [v1.1 (2021-04-24)](#v11-2021-04-24) + * [v1.0 (2021-01-15)](#v10-2021-01-15) + +## Version history + +### v3.5.0 (2023-05-25) + +* `BS_thread_pool.hpp` and `BS_thread_pool_light.hpp`: + * Added a new member function, `purge()`, to the full (non-light) thread pool. This function purges all the tasks waiting in the queue. Tasks that are currently running will not be affected, but any tasks still waiting in the queue will be removed and will never be executed by the threads. Please note that there is no way to restore the purged tasks. + * Fix a bug which caused `wait_for_tasks()` to only block the first thread that called it. Now it blocks every thread that calls it, which is the expected behavior. In addition, all related deadlock have now been completely resolved. This also applies to the variants `wait_for_tasks_duration()` and `wait_for_tasks_until()` in the non-light version. See [#110](https://github.com/bshoshany/thread-pool/pull/110). + * Note: You should never call `wait_for_tasks()` from within a thread of the same thread pool, as that will cause it to wait forever! This fix is relevant for situations when `wait_for_tasks()` is called from an auxiliary `std::thread` or a separate thread pool. + * `push_task()` and `submit()` now avoid creating unnecessary copies of the function object. This should improve performance, especially if large objects are involved. See [#90](https://github.com/bshoshany/thread-pool/pull/90). + * Optimized the way condition variables are used by the thread pool class. Shared variables are now modified while owning the mutex, but condition variables are notified after the mutex is released, if possible. See [#84](https://github.com/bshoshany/thread-pool/pull/84). + * Instead of a variable `tasks_total` to keep track of the total number of tasks (queued + running), the thread pool class now uses a variable `tasks_running` to keep track only of the number of running tasks, with the number of tasks in the queue obtained via `tasks.size()`. This makes more sense in terms of the internal logic of the class. + * All atomic variables have been converted to non-atomic. They are now all governed by `tasks_mutex`, so they do not need to be atomic. This eliminates redundant locking, and may improve performance a bit. + * `running` has been renamed to `workers_running` and `task_done_cv` has been renamed to `tasks_done_cv`. + * The worker now only notifies thi condition variable `tasks_done_cv` if all the tasks are done, not just a single task. Checking if the tasks are done is cheaper than notifying the condition variable, so since the worker no longer notifies the condition variable every single time it finishes a task, this should improve performance a bit if `wait_for_tasks()` is used. +* `BS_thread_pool_test.cpp`: + * Combined the tests for the full and light versions into one program. The file `BS_thread_pool_light_test.cpp` has been removed. + * The tests for the light version are now much more comprehensive. The only features that are not tested in the light version are those that do not exist in it. + * Added a test for the new `purge()` member function. + * Added a test to ensure that `push_task()` and `submit()` do not create unnecessary copies of the function object. + * Added a test to ensure that `push_task()` and `submit()` correctly accept arguments passed by value, reference, and constant reference. + * Added a test to ensure that `wait_for_tasks()` blocks all external threads that call it. + * `_CRT_SECURE_NO_WARNINGS` is now set only if it has not already been defined, to prevent errors in MSVC projects which already have it set as part of the default build settings. See [#72](https://github.com/bshoshany/thread-pool/pull/72). +* `README.md`: + * Added documentation for the new `purge()` member function. + * Added an explanation for how to pass arguments by reference or constant reference when submitting functions to the queue, using the wrappers `std::ref()` and `std::cref()` respectively. See [#83](https://github.com/bshoshany/thread-pool/issues/83). + * Added a link to [my lecture notes](https://baraksh.com/CSE701/notes.php) for a course taught at McMaster University, for the benefit of beginner C++ programmers who wish to learn some of the advanced techniques and programming practices used in developing this library. + * Removed the sample test results, since the complete log file (including the deadlock tests) is now over 500 lines long. +* Other: + * A `.clang-format` file with the project's formatting conventions is now included in the GitHub repository. The pull request template now asks to format any new code using this file, so that it is consistent with the rest of the library. + * A PowerShell script, `BS_thread_pool_test.ps1`, is now provided in the GitHub repository to make running the test on multiple compilers and operating systems easier. Since it is written in PowerShell, it is fully portable and works on Windows, Linux, and macOS. The script will automatically detect if Clang, GCC, and/or MSVC are available, compile the test program using each available compiler, and then run each compiled test program 5 times and report on any errors. The pull request template now recommends using this script for testing. + * Since the root folder has become a bit crowded, the header files `BS_thread_pool.hpp` and `BS_thread_pool_light.hpp` have been moved to the `include` subfolder, and the test file `BS_thread_pool_test.cpp` has been moved to the `tests` subfolder, which also contains the new test script `BS_thread_pool_test.ps1`. + +### v3.4.0 (2023-05-12) + +* `BS_thread_pool.hpp` and `BS_thread_pool_light.hpp`: + * Resolved an issue which could have caused `tasks_total` to not be synchronized in some cases. See [#70](https://github.com/bshoshany/thread-pool/pull/70). + * Resolved a deadlock which could rarely be caused when the pool was destructed or reset. See [#93](https://github.com/bshoshany/thread-pool/pull/93), [#100](https://github.com/bshoshany/thread-pool/pull/100), [#107](https://github.com/bshoshany/thread-pool/pull/107), and [#108](https://github.com/bshoshany/thread-pool/pull/108). + * Resolved a deadlock which could be caused when `wait_for_tasks()` was called more than once. + * Two new member functions have been added to the non-light version: `wait_for_tasks_duration()` and `wait_for_tasks_until()`. They allow waiting for the tasks to complete, but with a timeout. `wait_for_tasks_duration()` will stop waiting after the specified duration has passed, and `wait_for_tasks_until()` will stop waiting after the specified time point has been reached. + * Renamed `BS_THREAD_POOL_VERSION` in `BS_thread_pool_light.hpp` to `BS_THREAD_POOL_LIGHT_VERSION` and removed the `[light]` tag. This allows including both header files in the same program in case we want to use both the light and non-light thread pools simultaneously. +* `BS_thread_pool_test.cpp` and `BS_thread_pool_light_test.cpp`: + * Fixed an issue that caused a compilation error when using MSVC and including `Windows.h`. See [#72](https://github.com/bshoshany/thread-pool/pull/72). + * The number and size of the vectors in the performance test (`BS_thread_pool_test.cpp` only) are now guaranteed to be multiples of the number of threads, for optimal performance. + * In `count_unique_threads()`, moved the condition variables and mutexes to the function scope to prevent cluttering the global scope. + * Three new tests have been added to `BS_thread_pool_test.cpp` to check the deadlocks issue that were resolved in this release (see above). The tests rely on the new wait for tasks with timeout feature, so they are not available in the light version. + * One test checks for deadlocks when calling `wait_for_tasks()` more than once. + * Two tests check for deadlocks when destructing and resetting the pool respectively. They are turned off by default, since they take a long time to complete, but can be turned on by setting `enable_long_deadlock_tests` to `true`. + * Two new tests have been added to the non-light version to check the new member functions `wait_for_tasks_duration()` and `wait_for_tasks_until()`. + * The test programs now return the number of failed tests upon exit, instead of just 1 if any number of tests failed, which was the case in previous versions. Also, if any tests failed, `std::quick_exit()` is invoked instead of `return`, to avoid getting stuck due to any lingering tasks or deadlocks. +* `README.md`: + * Added documentation for the two new member functions, `wait_for_tasks_duration()` and `wait_for_tasks_until()`. + * Fixed Markdown rendering incorrectly on Visual Studio. See [#77](https://github.com/bshoshany/thread-pool/pull/77). + * The sample performance tests are now taken from a 40-core / 80-thread dual-CPU computing node, which is a more typical use case for high-performance scientific software. + +### v3.3.0 (2022-08-03) + +* `BS_thread_pool.hpp`: + * The public member variable `paused` of `BS::thread_pool` has been made private for future-proofing (in case future versions implement a more involved pausing mechanism) and better encapsulation. It is now accessible only via the `pause()`, `unpause()`, and `is_paused()` member functions. In other words: + * Replace `pool.paused = true` with `pool.pause()`. + * Replace `pool.paused = false` with `pool.unpause()`. + * Replace `if (pool.paused)` (or similar) with `if (pool.is_paused())`. + * The public member variable `f` of `BS::multi_future` has been renamed to `futures` for clarity, and has been made private for encapsulation and simplification purposes. Instead of operating on the vector `futures` itself, you can now use the `[]` operator of the `BS::multi_future` to access the future at a specific index directly, or the `push_back()` member function to append a new future to the list. The `size()` member function tells you how many futures are currently stored in the object. + * The explicit casts of `std::endl` and `std::flush`, added in v3.2.0 to enable flushing a `BS::synced_stream`, caused ODR (One Definition Rule) violations if `BS_thread_pool.hpp` was included in two different translation units, since they were mistakenly not defined as `inline`. To fix this, I decided to make them static members of `BS::synced_stream` instead of global variables, which also makes the code better organized in my opinion. These objects can now be accessed as `BS::synced_stream::endl` and `BS::synced_stream::flush`. I also added an example for how to use them in `README.md`. See [#64](https://github.com/bshoshany/thread-pool/issues/64). +* `BS_thread_pool_light.hpp`: + * This package started out as a very lightweight thread pool, but over time has expanded to include many additional features, and at the time of writing it has a total of 340 lines of code, including all the helper classes. Therefore, I have decided to bundle a light version of the thread pool in a separate and stand-alone header file, `BS_thread_pool_light.hpp`, with only 170 lines of code (half the size of the full package). This file does not contain any of the helper classes, only a new `BS::thread_pool_light` class, which is a minimal thread pool with only the 5 most basic member functions: + * `get_thread_count()` + * `push_loop()` + * `push_task()` + * `submit()` + * `wait_for_tasks()` + * A separate test program `BS_thread_pool_light_test.cpp` tests only the features of the lightweight `BS::thread_pool_light` class. In the spirit of minimalism, it does not generate a log file and does not do any benchmarks. + * To be perfectly clear, each header file is 100% stand-alone. If you wish to use the full package, you only need `BS_thread_pool.hpp`, and if you wish to use the light version, you only need `BS_thread_pool_light.hpp`. Only a single header file needs to be included in your project. + +### v3.2.0 (2022-07-28) + +* `BS_thread_pool.hpp`: + * Main `BS::thread_pool` class: + * Added a new member function, `push_loop()`, which does the same thing as `parallelize_loop()`, except that it does not return a `BS::multi_future` with the futures for each block. Just like `push_task()` vs. `submit()`, this avoids the overhead of creating the futures, but the user must use `wait_for_tasks()` or some other method to ensure that the loop finishes executing, otherwise bad things will happen. + * `push_task()` and `submit()` now utilize perfect forwarding in order to support more types of tasks - in particular member functions, which in previous versions could not be submitted unless wrapped in a lambda. To submit a member function, use the syntax `submit(&class::function, &object, args)`. More information can be found in `README.md`. See [#9](https://github.com/bshoshany/thread-pool/issues/9). + * `push_loop()` and `parallelize_loop()` now have overloads where the first argument (the first index in the loop) is omitted, in which case it is assumed to be 0. This is for convenience, as the case where the first index is 0 is very common. + * Helper classes: + * `BS::synced_stream` now utilizes perfect forwarding in the member functions `print()` and `println()`. + * Previously, it was impossible to pass the flushing manipulators `std::endl` and `std::flush` to `print()` and `println()`, since the compiler could not figure out which template specializations to use. The new objects `BS::endl` and `BS::flush` are explicit casts of these manipulators, whose sole purpose is to enable passing them to `print()` and `println()`. + * `BS::multi_future::get()` now rethrows exceptions generated by the futures, even if the futures return `void`. See [#62](https://github.com/bshoshany/thread-pool/pull/62). + * Added a new helper class, `BS::blocks`, which is used by `parallelize_loop()` and `push_loop()` to divide a range into blocks. This class is not documented in `README.md`, as it most likely will not be of interest to most users, but it is still publicly available, in case you want to parallelize something manually but still benefit from the built-in algorithm for splitting a range into blocks. +* `BS_thread_pool_test.cpp`: + * Added plenty of new tests for the new features described above. + * Fixed a bug in `count_unique_threads()` that caused it to get stuck on certain systems. + * `dual_println()` now also flushes the stream using `BS::endl`, so that if the test gets stuck, the log file will still contain everything up to that point. (Note: It is a common misconception that `std::endl` and `'\n'` are interchangeable. `std::endl` not only prints a newline character, it also flushes the stream, which is not always desirable, as it may reduce performance.) + * The performance test has been modified as follows: + * Instead of generating random vectors using `std::mersenne_twister_engine`, which proved to be inconsistent across different compilers and systems, the test now generates each element via an arbitrarily-chosen numerical operation. In my testing, this provided much more consistent results. + * Instead of using a hard-coded vector size, a suitable vector size is now determined dynamically at runtime. + * Instead of using `parallelize_loop()`, the test now uses the new `push_loop()` function to squeeze out a bit more performance. + * Instead of setting the test parameters to achieve a fixed single-threaded mean execution time of 300 ms, the test now aims to achieve a fixed multi-threaded mean execution time of 50 ms when the number of blocks is equal to the number of threads. This allows for more reliable results on very fast CPUs with a very large number of threads, where the mean execution time when using all the threads could previously be below a statistically significant value. + * The number of vectors is now restricted to be a multiple of the number of threads, so that the blocks are always all of the same size. +* `README.md`: + * Added instructions and examples for the new features described above. + * Rewrote the documentation for `parallelize_loop()` to make it clearer. + +### v3.1.0 (2022-07-13) + +* `BS_thread_pool.hpp`: + * Fixed an issue where `wait_for_tasks()` would sometimes get stuck if `push_task()` was executed immediately before `wait_for_tasks()`. + * Both the thread pool constructor and the `reset()` member function now determine the number of threads to use in the pool as follows. If the parameter is a positive number, then the pool will be created with this number of threads. If the parameter is non-positive, or a parameter was not supplied, then the pool will be created with the total number of hardware threads available, as obtained from `std::thread::hardware_concurrency()`. If the latter returns a non-positive number for some reason, then the pool will be created with just one thread. See [#51](https://github.com/bshoshany/thread-pool/issues/51) and [#52](https://github.com/bshoshany/thread-pool/issues/52). + * Added the `[[nodiscard]]` attribute to classes and class members, in order to warn the user when accidentally discarding an important return value, such as a future or the return value of a function with no useful side-effects. For example, if you use `submit()` and don't save the future it returns, the compiler will now generate a warning. (If a future is not needed, then you should use `push_task()` instead.) + * Removed the `explicit` specifier from all constructors, as it prevented the default constructor from being used with static class members. See [#48](https://github.com/bshoshany/thread-pool/issues/48>). +* `BS_thread_pool_test.cpp`: + * Improved `count_unique_threads()` using condition variables, to ensure that each thread in the pool runs at least one task regardless of how fast it takes to run the tasks. + * When appropriate, `check()` now explicitly reports what the obtained result was and what it was expected to be. + * `check_task_monitoring()` and `check_pausing()` now explicitly report the results of the monitoring at each step. + * Changed all instances of `std::vector>` to `std::unique_ptr[]>`. See [#44](https://github.com/bshoshany/thread-pool/issues/44). + * Converted a few more C-style casts to C++ cast expressions. +* `README.md`: + * Added instructions for using this package with the [Conan](https://conan.io/) C/C++ package manager. Please refer to [this package's page on ConanCenter](https://conan.io/center/bshoshany-thread-pool) to learn how to use Conan to include this package in your project with various build systems. +* If you found this project useful, please consider [starring it on GitHub](https://github.com/bshoshany/thread-pool/stargazers)! This allows me to see how many people are using my code, and motivates me to keep working to improve it. + +### v3.0.0 (2022-05-30) + +* This is a major new release with many changes and improvements! Please note that code written using previous releases will need to be slightly modified to work with the new release. The changes needed to migrate to the new API are explicitly indicated below for your convenience. +* Breaking changes to the library header file: + * The header file has been renamed to `BS_thread_pool.hpp` to avoid potential conflict with other thread pool libraries. + * **API migration:** The library must now be included by invoking `#include "BS_thread_pool.hpp"`. + * All the definitions in the library, including the `thread_pool` class and the helper classes, are now located in the namespace `BS`. This namespace will also be used for my other C++ projects, and is intended to ensure consistency between my projects while avoiding potential name conflicts with other libraries. + * **API migration:** The thread pool class should now be invoked as `BS::thread_pool`. Alternatively, it is possible to employ `using BS::thread_pool` or even `using namespace BS` and then invoke `thread_pool` directly. Same for the `BS::synced_stream` and `BS::timer` helper classes. + * The macro `THREAD_POOL_VERSION`, which contains the version number and release date of the library, has been renamed to `BS_THREAD_POOL_VERSION` to avoid potential conflicts. + * **API migration:** The version must now be read from the macro `BS_THREAD_POOL_VERSION`. + * The public member `sleep_duration` has been removed. The thread pool now uses condition variables instead of sleep to facilitate waiting. This significantly improves performance (by 10%-50% in my testing), drastically decreases idle CPU utilization, and eliminates the need to set an optimal sleep time. This was a highly-requested change; see [issue #1](https://github.com/bshoshany/thread-pool/issues/1), [issue #12](https://github.com/bshoshany/thread-pool/issues/12), and [pull request #23](https://github.com/bshoshany/thread-pool/pull/23). + * **API migration:** Remove any code that relates to the public member `sleep_duration`. + * The template specializations for `submit()` have been merged. Now instead of two versions, one for functions with a return value and one for functions without a return value, there is just one version, which can accept any function. This makes the code more compact (and elegant). If a function with no return value is submitted, an `std::future` is returned (the previous version returned an `std::future`) + * **API migration:** To wait for a task with no return value, simply call `wait()` or `get()` on the corresponding `std::future`. + * `parallelize_loop()` now returns a future in the form of a new `BS::multi_future` helper class template. The member function `wait()` of this future allows waiting until all of the loop's blocks finish executing. In previous versions, calling `parallelize_loop()` both parallelized the loop and waited for the blocks to finish; now it is possible to do other stuff while the loop executes. + * **API migration:** Since `parallelize_loop()` no longer automatically blocks, you should either store the result in a `BS::multi_future` object and call its `wait()` member function, or simply call `parallelize_loop().wait()` to reproduce the old behavior. +* Non-breaking changes to the library header file: + * It is now possible to use `parallelize_loop()` with functions that have return values and get these values from all blocks at once through the `get()` member function of the `BS::multi_future`. + * The template specializations for `push_task()` have been merged. Now instead of two versions, one for functions with arguments and one for functions without arguments, there is just one version, which can accept any function. + * Constructors have been made `explicit`. See [issue #28](https://github.com/bshoshany/thread-pool/issues/28). + * `submit()` now uses `std::make_shared` instead of `new` to create the shared pointer. This means only one memory allocation is performed instead of two, which should improve performance. In addition, all unique pointers are now created using `std::make_unique`. + * A new helper class template, `BS::multi_future`, has been added. It's basically just a wrapper around `std::vector>`. This class is used by the new implementation of `parallelize_loop()` to allow waiting for the entire loop, consisting of multiple tasks with their corresponding futures, to finish executing. + * `BS::multi_future` can also be used independently to handle multiple futures at once. For example, you can now keep track of several groups of tasks by storing their futures inside separate `BS::multi_future` objects and use either `wait()` to wait for all tasks in a specific group to finish or `get()` to get an `std::vector` with the return values of every task in the group. + * Integer types are now chosen in a smarter way to improve portability, allow for better compatibility with 32-bit systems, and prevent potential conversion errors. + * Added a new type, `BS::concurrency_t`, equal to the return type of `std::thread::hardware_concurrency()`. This is probably pointless, since the C++ standard requires this to be `unsigned int`, but it seems to me to make the code slightly more portable, in case some non-conforming compiler chooses to use a different integer type. + * C-style casts have been converted to C++ cast expressions for added clarity. + * Miscellaneous minor optimizations and style improvements. +* Changes to the test program: + * The program has been renamed to `BS_thread_pool_test.cpp` to avoid potential conflict with other thread pool libraries. + * The program now returns `EXIT_FAILURE` if any of the tests failed, for automation purposes. See [pull request #42](https://github.com/bshoshany/thread-pool/pull/42). + * Fixed incorrect check order in `check_task_monitoring()`. See [pull request #43](https://github.com/bshoshany/thread-pool/pull/43). + * Added a new test for `parallelize_loop()` with a return value. + * Improved some of the tests to make them more reliable. For example, `count_unique_threads()` now uses futures (stored in a `BS::multi_future` object). + * The program now uses `std::vector` instead of matrices, for both consistency checks and benchmarks, in order to simplify the code and considerably reduce its length. + * The benchmarks have been simplified. There's now only one test: filling a specific number of vectors of fixed size with random values. This may be replaced with something more practical in a future released, but at least on the systems I've tested on, it does demonstrate a very significant multi-threading speedup. + * In addition to multi-threaded tests with different numbers of tasks, the benchmark now also includes a single-threaded test. This allows for more accurate benchmarks compared to previous versions, as the (slight) parallelization overhead is now taken into account when calculating the maximum speedup. + * The program decides how many vectors to use for benchmarking by testing how many are needed to reach a target duration in the single-threaded test. This ensures that the test takes approximately the same amount of time on different systems, and is thus more consistent and portable. + * Miscellaneous minor optimizations and style improvements. +* Changes to `README.md`: + * Many sections have been rewritten and/or polished. + * Explanations and examples of all the new features have been added. + * Added an acknowledgements section. +* Miscellaneous changes: + * Added a `CITATION.bib` file (in BibTeX format) to the GitHub repository. You can use it to easily cite this package if you use it in any research papers. + * Added a `CITATION.cff` file (in YAML format) to the GitHub repository. This should add [an option to get a citation in different formats](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files) directly from GitHub repository by clicking on "cite this repository" on the sidebar to the right. + * Added templates for GitHub issues and pull requests. + +### v2.0.0 (2021-08-14) + +* From now on, version numbers will adhere to the [Semantic Versioning](https://semver.org/) specification in the format **major.minor.patch**. +* A file named `thread_pool_test.cpp` has been added to the package. It will perform automated tests of all aspects of the package, and benchmark some multithreaded matrix operations. Please run it on your system and [submit a bug report](https://github.com/bshoshany/thread-pool/issues) if any of the tests fail. In addition, the code is thoroughly documented, and is meant to serve as an extensive example of how to properly use the package. +* The package is now available through [vcpkg](https://github.com/microsoft/vcpkg). Instructions for how to install it have been added to `README.md`. See [this pull request](https://github.com/bshoshany/thread-pool/pull/18). +* The package now defines a macro `THREAD_POOL_VERSION`, which returns the version number and release date of the thread pool library as a string. +* `parallelize_loop()` has undergone some major changes (and is now incompatible with v1.x): + * The second argument is now the index **after** the last index, instead of the last index itself. This is more consistent with C++ conventions (e.g. standard library algorithms) where the range is always `[first, last)`. For example, for an array with `n` indices, instead of `parallelize_loop(0, n - 1, ...)` you should now write `parallelize_loop(0, n, ...)`. + * The `loop` function is now only called once per block, instead of once per index, as was the case before. This should provide a performance boost due to significantly reducing the number of function calls, and it also allows you to conserve resources by using them only once per block instead of once per index (an example can be found in the `random_matrix_generator` class in `thread_pool_test.cpp`). It also means that `loop` now takes two arguments: the first index in the block and the index after the last index in the block. Thus, `loop(start, end)` should typically involve a loop of the form `for (T i = start; i < end; i++)`. + * The first and last indices can now be of two different integer types. Previously, `parallelize_loop(0, i, ...)` did not work if `i` was not an `int`, because `0` was interpreted as `int`, and the two arguments had to be of the same type. Therefore, one had to use casting, e.g. `parallelize_loop((size_t)0, i)`, to make it work. Now this is no longer necessary; the common type is inferred automatically using `std::common_type_t`. + +### v1.9 (2021-07-29) + +* Fixed a bug in `reset()` which caused it to create the wrong number of threads. + +### v1.8 (2021-07-28) + +* The version history has become too long to be included in `README.md`, so I moved it to a separate file, `CHANGELOG.md`. +* A button to open this repository directly in Visual Studio Code has been added to the badges in `README.md`. +* An internal variable named `promise` has been renamed to `task_promise` to avoid any potential errors in case the user invokes `using namespace std`. +* `submit()` now catches exceptions thrown by the submitted task and forwards them to the future. See [this issue](https://github.com/bshoshany/thread-pool/issues/14). +* Eliminated compiler warnings that appeared when using the `-Weffc++` flag in GCC. See [this pull request](https://github.com/bshoshany/thread-pool/pull/17). + +### v1.7 (2021-06-02) + +* Fixed a bug in `parallelize_loop()` which prevented it from actually running loops in parallel, see [this issue](https://github.com/bshoshany/thread-pool/issues/11). + +### v1.6 (2021-05-26) + +* Since MSVC does not interpret `and` as `&&` by default, the previous release did not compile with MSVC unless the `/permissive-` or `/Za` compiler flags were used. This has been fixed in this version, and the code now successfully compiles with GCC, Clang, and MSVC. See [this pull request](https://github.com/bshoshany/thread-pool/pull/10). + +### v1.5 (2021-05-07) + +* This library now has a DOI for citation purposes. Information on how to cite it in publications has been added to the source code and to `README.md`. +* Added GitHub badges to `README.md`. + +### v1.4 (2021-05-05) + +* Added three new public member functions to monitor the tasks submitted to the pool: + * `get_tasks_queued()` gets the number of tasks currently waiting in the queue to be executed by the threads. + * `get_tasks_running()` gets the number of tasks currently being executed by the threads. + * `get_tasks_total()` gets the total number of unfinished tasks - either still in the queue, or running in a thread. + * Note that `get_tasks_running() == get_tasks_total() - get_tasks_queued()`. + * Renamed the private member variable `tasks_waiting` to `tasks_total` to make its purpose clearer. +* Added an option to temporarily pause the workers: + * When public member variable `paused` is set to `true`, the workers temporarily stop popping new tasks out of the queue, although any tasks already executed will keep running until they are done. Set to `false` again to resume popping tasks. + * While the workers are paused, `wait_for_tasks()` will wait for the running tasks instead of all tasks (otherwise it would wait forever). + * By utilizing the new pausing mechanism, `reset()` can now change the number of threads on-the-fly while there are still tasks waiting in the queue. The new thread pool will resume executing tasks from the queue once it is created. +* `parallelize_loop()` and `wait_for_tasks()` now have the same behavior as the worker function with regards to waiting for tasks to complete. If the relevant tasks are not yet complete, then before checking again, they will sleep for `sleep_duration` microseconds, unless that variable is set to zero, in which case they will call `std::this_thread::yield()`. This should improve performance and reduce CPU usage. +* Merged [this commit](https://github.com/bshoshany/thread-pool/pull/8): Fixed weird error when using MSVC and including `windows.h`. +* The `README.md` file has been reorganized and expanded. + +### v1.3 (2021-05-03) + +* Fixed [this issue](https://github.com/bshoshany/thread-pool/issues/3): Removed `std::move` from the `return` statement in `push_task()`. This previously generated a `-Wpessimizing-move` warning in Clang. The assembly code generated by the compiler seems to be the same before and after this change, presumably because the compiler eliminates the `std::move` automatically, but this change gets rid of the Clang warning. +* Fixed [this issue](https://github.com/bshoshany/thread-pool/issues/5): Removed a debugging message printed to `std::cout`, which was left in the code by mistake. +* Fixed [this issue](https://github.com/bshoshany/thread-pool/issues/6): `parallelize_loop()` no longer sends references for the variables `start` and `stop` when calling `push_task()`, which may lead to undefined behavior. +* A companion paper is now published at arXiv:2105.00613, including additional information such as performance tests on systems with up to 80 hardware threads. The `README.md` has been updated, and it is now roughly identical in content to the paper. + +### v1.2 (2021-04-29) + +* The worker function, which controls the execution of tasks by each thread, now sleeps by default instead of yielding. Previously, when the worker could not find any tasks in the queue, it called `std::this_thread::yield()` and then tried again. However, this caused the workers to have high CPU usage when idle, [as reported by some users](https://github.com/bshoshany/thread-pool/issues/1). Now, when the worker function cannot find a task to run, it instead sleeps for a duration given by the public member variable `sleep_duration` (in microseconds) before checking the queue again. The default value is `1000` microseconds, which I found to be optimal in terms of both CPU usage and performance, but your own optimal value may be different. +* If the constructor is called with an argument of zero for the number of threads, then the default value, `std::thread::hardware_concurrency()`, is used instead. +* Added a simple helper class, `timer`, which can be used to measure execution time for benchmarking purposes. +* Improved and expanded the documentation. + +### v1.1 (2021-04-24) + +* Cosmetic changes only. Fixed a typo in the Doxygen comments and added a link to the GitHub repository. + +### v1.0 (2021-01-15) + +* Initial release. diff --git a/external/include/common/thread-pool-3.5.0/CITATION.bib b/external/include/common/thread-pool-3.5.0/CITATION.bib new file mode 100644 index 000000000..51406fde5 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/CITATION.bib @@ -0,0 +1,13 @@ +@article{Shoshany2021_ThreadPool, + archiveprefix = {arXiv}, + author = {Barak Shoshany}, + doi = {10.5281/zenodo.4742687}, + eid = {arXiv:2105.00613}, + eprint = {2105.00613}, + journal = {arXiv e-prints}, + keywords = {Computer Science - Distributed, Parallel, and Cluster Computing, D.1.3, D.1.5}, + month = {May}, + primaryclass = {cs.DC}, + title = {{A C++17 Thread Pool for High-Performance Scientific Computing}}, + year = {2021} +} diff --git a/external/include/common/thread-pool-3.5.0/CITATION.cff b/external/include/common/thread-pool-3.5.0/CITATION.cff new file mode 100644 index 000000000..99bb72978 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/CITATION.cff @@ -0,0 +1,24 @@ +--- +authors: + - family-names: "Shoshany" + given-names: "Barak" + orcid: "https://orcid.org/0000-0003-2222-127X" +cff-version: "1.2.0" +date-released: "2021-05-03" +doi: "10.5281/zenodo.4742687" +license: "MIT" +message: "If you use this package in published research, please cite it as follows." +repository-code: "https://github.com/bshoshany/thread-pool" +title: "A C++17 Thread Pool for High-Performance Scientific Computing" +preferred-citation: + type: "article" + authors: + - family-names: "Shoshany" + given-names: "Barak" + orcid: "https://orcid.org/0000-0003-2222-127X" + doi: "10.5281/zenodo.4742687" + journal: "arXiv" + month: 5 + title: "A C++17 Thread Pool for High-Performance Scientific Computing" + url: "https://arxiv.org/abs/2105.00613" + year: 2021 diff --git a/external/include/common/thread-pool-3.5.0/LICENSE.txt b/external/include/common/thread-pool-3.5.0/LICENSE.txt new file mode 100644 index 000000000..365146c77 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Barak Shoshany + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/include/common/thread-pool-3.5.0/README.md b/external/include/common/thread-pool-3.5.0/README.md new file mode 100644 index 000000000..d6e0c8f51 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/README.md @@ -0,0 +1,1353 @@ +[![DOI:10.5281/zenodo.4742687](https://zenodo.org/badge/DOI/10.5281/zenodo.4742687.svg)](https://doi.org/10.5281/zenodo.4742687) +[![arXiv:2105.00613](https://img.shields.io/badge/arXiv-2105.00613-b31b1b.svg)](https://arxiv.org/abs/2105.00613) +[![License: MIT](https://img.shields.io/github/license/bshoshany/thread-pool)](https://github.com/bshoshany/thread-pool/blob/master/LICENSE.txt) +![Language: C++17](https://img.shields.io/badge/Language-C%2B%2B17-yellow) +![File size in bytes](https://img.shields.io/github/size/bshoshany/thread-pool/BS_thread_pool.hpp) +![GitHub last commit](https://img.shields.io/github/last-commit/bshoshany/thread-pool) +[![GitHub repo stars](https://img.shields.io/github/stars/bshoshany/thread-pool?style=social)](https://github.com/bshoshany/thread-pool) +[![Open in Visual Studio Code](https://img.shields.io/badge/-Open%20in%20Visual%20Studio%20Code-007acc)](https://vscode.dev/github/bshoshany/thread-pool) + +# `BS::thread_pool`: a fast, lightweight, and easy-to-use C++17 thread pool library + +By Barak Shoshany\ +Email: \ +Website: \ +GitHub: + +This is the complete documentation for v3.5.0 of the library, released on 2023-05-25. + +* [Introduction](#introduction) + * [Motivation](#motivation) + * [Overview of features](#overview-of-features) + * [Compiling and compatibility](#compiling-and-compatibility) + * [Installing using vcpkg](#installing-using-vcpkg) + * [Installing using Conan](#installing-using-conan) +* [Getting started](#getting-started) + * [Including the library](#including-the-library) + * [Constructors](#constructors) + * [Getting and resetting the number of threads in the pool](#getting-and-resetting-the-number-of-threads-in-the-pool) + * [Finding the version of the package](#finding-the-version-of-the-package) +* [Submitting and waiting for tasks](#submitting-and-waiting-for-tasks) + * [Submitting tasks to the queue with futures](#submitting-tasks-to-the-queue-with-futures) + * [Submitting tasks to the queue without futures](#submitting-tasks-to-the-queue-without-futures) + * [Manually waiting for all tasks to complete](#manually-waiting-for-all-tasks-to-complete) + * [Waiting with a timeout](#waiting-with-a-timeout) + * [Submitting class member functions to the queue](#submitting-class-member-functions-to-the-queue) + * [Passing task arguments by reference](#passing-task-arguments-by-reference) +* [Parallelizing loops](#parallelizing-loops) + * [Automatic parallelization of loops](#automatic-parallelization-of-loops) + * [Loops with return values](#loops-with-return-values) + * [Parallelizing loops without futures](#parallelizing-loops-without-futures) +* [Helper classes](#helper-classes) + * [Synchronizing printing to an output stream](#synchronizing-printing-to-an-output-stream) + * [Handling multiple futures at once](#handling-multiple-futures-at-once) + * [Measuring execution time](#measuring-execution-time) +* [Other features](#other-features) + * [Monitoring the tasks](#monitoring-the-tasks) + * [Pausing the workers](#pausing-the-workers) + * [Purging tasks](#purging-tasks) + * [Exception handling](#exception-handling) +* [Testing the package](#testing-the-package) + * [Automated tests](#automated-tests) + * [Performance tests](#performance-tests) +* [The light version of the package](#the-light-version-of-the-package) +* [About the project](#about-the-project) + * [Issue and pull request policy](#issue-and-pull-request-policy) + * [Acknowledgements](#acknowledgements) + * [Starring the repository](#starring-the-repository) + * [Copyright and citing](#copyright-and-citing) + * [Learning more about C++](#learning-more-about-c) + +## Introduction + +### Motivation + +Multithreading is essential for modern high-performance computing. Since C\+\+11, the C++ standard library has included built-in low-level multithreading support using constructs such as `std::thread`. However, `std::thread` creates a new thread each time it is called, which can have a significant performance overhead. Furthermore, it is possible to create more threads than the hardware can handle simultaneously, potentially resulting in a substantial slowdown. + +The library presented here contains a C++ thread pool class, `BS::thread_pool`, which avoids these issues by creating a fixed pool of threads once and for all, and then continuously reusing the same threads to perform different tasks throughout the lifetime of the program. By default, the number of threads in the pool is equal to the maximum number of threads that the hardware can run in parallel. + +The user submits tasks to be executed into a queue. Whenever a thread becomes available, it retrieves the next task from the queue and executes it. The pool automatically produces an `std::future` for each task, which allows the user to wait for the task to finish executing and/or obtain its eventual return value, if applicable. Threads and tasks are autonomously managed by the pool in the background, without requiring any input from the user aside from submitting the desired tasks. + +The design of this package was guided by four important principles. First, *compactness*: the entire library consists of just one small self-contained header file, with no other components or dependencies. Second, *portability*: the package only utilizes the C\+\+17 standard library, without relying on any compiler extensions or 3rd-party libraries, and is therefore compatible with any modern standards-conforming C\+\+17 compiler on any platform. Third, *ease of use*: the package is extensively documented, and programmers of any level should be able to use it right out of the box. + +The fourth and final guiding principle is *performance*: each and every line of code in this library was carefully designed with maximum performance in mind, and performance was tested and verified on a variety of compilers and platforms. Indeed, the library was originally designed for use in the author's own computationally-intensive scientific computing projects, running both on high-end desktop/laptop computers and high-performance computing nodes. + +Other, more advanced multithreading libraries may offer more features and/or higher performance. However, they typically consist of a vast codebase with multiple components and dependencies, and involve complex APIs that require a substantial time investment to learn. This library is not intended to replace these more advanced libraries; instead, it was designed for users who don't require very advanced features, and prefer a simple and lightweight package that is easy to learn and use and can be readily incorporated into existing or new projects. + +### Overview of features + +* **Fast:** + * Built from scratch with maximum performance in mind. + * Suitable for use in high-performance computing nodes with a very large number of CPU cores. + * Compact code, to reduce both compilation time and binary size. + * Reusing threads avoids the overhead of creating and destroying them for individual tasks. + * A task queue ensures that there are never more threads running in parallel than allowed by the hardware. +* **Lightweight:** + * Single header file: simply `#include "BS_thread_pool.hpp"` and you're all set! + * Header-only: no need to install or build the library. + * Self-contained: no external requirements or dependencies. + * Portable: uses only the C++ standard library, and works with any C++17-compliant compiler. + * Only 247 lines of code, excluding comments, blank lines, and lines containing only a single brace. + * A stand-alone "light version" of the C++ thread pool is also available in the `BS_thread_pool_light.hpp` header file, with only 115 lines of code. +* **Easy to use:** + * Very simple operation, using a handful of member functions. + * Every task submitted to the queue using the `submit()` member function automatically generates an `std::future`, which can be used to wait for the task to finish executing and/or obtain its eventual return value. + * Loops can be automatically parallelized into any number of parallel tasks using the `parallelize_loop()` member function, which returns a `BS::multi_future` (see below) that can be used to track the execution of all parallel tasks at once. + * If futures are not needed, tasks may be submitted using `push_task()`, and loops can be parallelized using `push_loop()` - sacrificing convenience for even greater performance. + * The code is thoroughly documented using Doxygen comments - not only the interface, but also the implementation, in case the user would like to make modifications. + * The included test program `BS_thread_pool_test.cpp` can be used to perform exhaustive automated tests and benchmarks, and also serves as a comprehensive example of how to properly use the package. +* **Helper classes:** + * Track the execution of multiple futures at once using the `BS::multi_future` helper class. + * Synchronize output to a stream from multiple threads in parallel using the `BS::synced_stream` helper class. + * Easily measure execution time for benchmarking purposes using the `BS::timer` helper class. +* **Additional features:** + * Easily wait for all tasks in the queue to complete using the `wait_for_tasks()`, `wait_for_tasks_duration()`, and `wait_for_tasks_until()` member functions. + * Change the number of threads in the pool safely and on-the-fly as needed using the `reset()` member function. + * Monitor the number of queued and/or running tasks using the `get_tasks_queued()`, `get_tasks_running()`, and `get_tasks_total()` member functions. + * Freely pause and resume the pool using the `pause()`, `unpause()`, and `is_paused()` member functions; when paused, threads do not retrieve new tasks out of the queue. + * Purge all tasks currently waiting in the queue with the `purge()` member function. + * Catch exceptions thrown by tasks submitted using `submit()` or `parallelize_loop()` from the main thread through their futures. + * Submit class member functions to the pool, either applied to a specific object or from within the object itself. + * Pass arguments to tasks by value, reference, or constant reference. + * Under continuous and active development. Bug reports and feature requests are welcome, and should be made via [GitHub issues](https://github.com/bshoshany/thread-pool/issues). + +### Compiling and compatibility + +This library should successfully compile on any C++17 standard-compliant compiler, on all operating systems and architectures for which such a compiler is available. Compatibility was verified with a 24-core (8P+16E) / 32-thread Intel i9-13900K CPU using the following compilers and platforms: + +* Windows 11 build 22621.1702: + * [Clang](https://clang.llvm.org/) v16.0.4 + * [GCC](https://gcc.gnu.org/) v13.1.0 ([WinLibs build](https://winlibs.com/)) + * [MSVC](https://docs.microsoft.com/en-us/cpp/) v19.36.32532 +* Ubuntu 23.04: + * [Clang](https://clang.llvm.org/) v16.0.0 + * [GCC](https://gcc.gnu.org/) v13.0.1 + +In addition, this library was tested on a [Digital Research Alliance of Canada](https://alliancecan.ca/en) node equipped with two 20-core / 40-thread Intel Xeon Gold 6148 CPUs (for a total of 40 cores and 80 threads), running CentOS Linux 7.9.2009, using [GCC](https://gcc.gnu.org/) v12.2.0. + +The test program `BS_thread_pool_test.cpp` was compiled without warnings (with the warning flags `-Wall -Wextra -Wconversion -Wsign-conversion -Wpedantic -Weffc++ -Wshadow` in GCC/Clang and `/W4` in MSVC), executed, and successfully completed all [automated tests](#testing-the-package) and benchmarks using all of the compilers and systems mentioned above. + +As this library requires C\+\+17 features, the code must be compiled with C\+\+17 support: + +* For Clang or GCC, use the `-std=c++17` flag. On Linux, you will also need to use the `-pthread` flag to enable the POSIX threads library. +* For MSVC, use `/std:c++17`, and preferably also `/permissive-` to ensure standards conformance. + +For maximum performance, it is recommended to compile with all available compiler optimizations: + +* For Clang or GCC, use the `-O3` flag. +* For MSVC, use `/O2`. + +As an example, to compile the test program `BS_thread_pool_test.cpp` with warnings and optimizations, it is recommended to use the following commands: + +* On Linux with GCC: `g++ BS_thread_pool_test.cpp -std=c++17 -O3 -Wall -Wextra -Wconversion -Wsign-conversion -Wpedantic -Weffc++ -Wshadow -pthread -o BS_thread_pool_test` +* On Linux with Clang: replace `g++` with `clang++`. +* On Windows with GCC or Clang: replace `-o BS_thread_pool_test` with `-o BS_thread_pool_test.exe` and remove `-pthread`. +* On Windows with MSVC: `cl BS_thread_pool_test.cpp /std:c++17 /permissive- /O2 /W4 /EHsc /Fe:BS_thread_pool_test.exe` + +### Installing using vcpkg + +If you are using the [vcpkg](https://github.com/microsoft/vcpkg) C/C++ library manager, you can easily download and install this package with the following commands. + +On Linux/macOS: + +```none +./vcpkg install bshoshany-thread-pool +``` + +On Windows: + +```none +.\vcpkg install bshoshany-thread-pool:x86-windows bshoshany-thread-pool:x64-windows +``` + +The C++ thread pool will then be available automatically in the build system you integrated vcpkg with (e.g. MSBuild or CMake). Simply write `#include "BS_thread_pool.hpp"` in any project to use the thread pool, without having to copy to file into the project first. I will update the vcpkg port with each new release, so it will be updated automatically when you run `vcpkg upgrade`. + +Please see the [vcpkg repository](https://github.com/microsoft/vcpkg) for more information on how to use vcpkg. + +### Installing using Conan + +If you are using the [Conan](https://conan.io/) C/C++ package manager, please refer to [this package's page on ConanCenter](https://conan.io/center/bshoshany-thread-pool) to learn how to use Conan to include this package in your project with various build systems. + +## Getting started + +### Including the library + +If you are not using a C++ library manager (such as vcpkg), simply download the [latest release](https://github.com/bshoshany/thread-pool/releases) from the GitHub repository, place the single header file `BS_thread_pool.hpp` from the `include` folder of the repository in the desired folder, and include it in your program: + +```cpp +#include "BS_thread_pool.hpp" +``` + +The thread pool will now be accessible via the `BS::thread_pool` class. + +### Constructors + +The default constructor creates a thread pool with as many threads as the hardware can handle concurrently, as reported by the implementation via `std::thread::hardware_concurrency()`. This is usually determined by the number of cores in the CPU. If a core is hyperthreaded, it will count as two threads. For example: + +```cpp +// Constructs a thread pool with as many threads as available in the hardware. +BS::thread_pool pool; +``` + +Optionally, a number of threads different from the hardware concurrency can be specified as an argument to the constructor. However, note that adding more threads than the hardware can handle will **not** improve performance, and in fact will most likely hinder it. This option exists in order to allow using **less** threads than the hardware concurrency, in cases where you wish to leave some threads available for other processes. For example: + +```cpp +// Constructs a thread pool with only 12 threads. +BS::thread_pool pool(12); +``` + +If your program's main thread only submits tasks to the thread pool and waits for them to finish, and does not perform any computationally intensive tasks on its own, then it is recommended to use the default value for the number of threads. This ensures that all of the threads available in the hardware will be put to work while the main thread waits. + +However, if your main thread does perform computationally intensive tasks on its own, then it is recommended to use the value `std::thread::hardware_concurrency() - 1` for the number of threads. In this case, the main thread plus the thread pool will together take up exactly all the threads available in the hardware. + +### Getting and resetting the number of threads in the pool + +The member function `get_thread_count()` returns the number of threads in the pool. This will be equal to `std::thread::hardware_concurrency()` if the default constructor was used. + +It is generally unnecessary to change the number of threads in the pool after it has been created, since the whole point of a thread pool is that you only create the threads once. However, if needed, this can be done, safely and on-the-fly, using the `reset()` member function. + +`reset()` will wait for all currently running tasks to be completed, but will leave the rest of the tasks in the queue. Then it will destroy the thread pool and create a new one with the desired new number of threads, as specified in the function's argument (or the hardware concurrency if no argument is given). The new thread pool will then resume executing the tasks that remained in the queue and any new submitted tasks. + +### Finding the version of the package + +If desired, the version of this package may be read during compilation time from the macro `BS_THREAD_POOL_VERSION`. The value will be a string containing the version number and release date. For example: + +```cpp +std::cout << "Thread pool library version is " << BS_THREAD_POOL_VERSION << ".\n"; +``` + +Sample output: + +```none +Thread pool library version is v3.5.0 (2023-05-25). +``` + +This can be used, for example, to allow the same code to work with several incompatible versions of the library. + +## Submitting and waiting for tasks + +### Submitting tasks to the queue with futures + +A task can be any function, with zero or more arguments, and with or without a return value. Once a task has been submitted to the queue, it will be executed as soon as a thread becomes available. Tasks are executed in the order that they were submitted (first-in, first-out). + +The member function `submit()` is used to submit tasks to the queue. The first argument is the function to execute, and the rest of the arguments are the arguments to pass to the function, if any. The return value is an `std::future` associated to the task. For example: + +```cpp +// Submit a task without arguments to the queue, and get a future for it. +auto my_future = pool.submit(task); +// Submit a task with one argument to the queue, and get a future for it. +auto my_future = pool.submit(task, arg); +// Submit a task with two arguments to the queue, and get a future for it. +auto my_future = pool.submit(task, arg1, arg2); +``` + +If the submitted function has a return value of type `T`, then the future will be of type `std::future`, and will be set to the return value when the function finishes its execution. If the submitted function does not have a return value, then the future will be an `std::future`, which will not return any value but may still be used to wait for the function to finish. + +Using `auto` for the return value of `submit()` means the compiler will automatically detect which instance of the template `std::future` to use. However, specifying the particular type `std::future`, as in the examples below, is recommended for increased readability. + +To wait until the task finishes, use the member function `wait()` of the future. To obtain the return value, use the member function `get()`, which will also automatically wait for the task to finish if it hasn't yet. For example: + +```cpp +// Submit a task and get a future. +auto my_future = pool.submit(task); +// Do some other stuff while the task is executing. +do_stuff(); +// Get the task's return value from the future, waiting for it to finish running if needed. +auto my_return_value = my_future.get(); +``` + +Here are some more concrete examples. The following program will print out `42`: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool; + std::future my_future = pool.submit([] { return 42; }); + std::cout << my_future.get(); +} +``` + +Here we used a [lambda expression](https://en.cppreference.com/w/cpp/language/lambda) to quickly define the function on-the-fly. However, we can also use a previously-defined function: + +```cpp +#include "BS_thread_pool.hpp" + +int the_answer() +{ + return 42; +} + +int main() +{ + BS::thread_pool pool; + std::future my_future = pool.submit(the_answer); + std::cout << my_future.get(); +} +``` + +The following is an example of submitting a function with arguments: + +```cpp +#include "BS_thread_pool.hpp" + +int multiply(const int a, const int b) +{ + return a * b; +} + +int main() +{ + BS::thread_pool pool; + std::future my_future = pool.submit(multiply, 6, 7); + std::cout << my_future.get(); +} +``` + +Finally, here is an example of submitting a function with no return value and then using the future to wait for it to finish executing: + +```cpp +#include "BS_thread_pool.hpp" + +void sleep() +{ + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); +} + +int main() +{ + BS::thread_pool pool; + std::future my_future = pool.submit(sleep); + std::cout << "Waiting... "; + my_future.wait(); + std::cout << "Done."; +} +``` + +Here, the command `std::this_thread::sleep_for(std::chrono::milliseconds(1000))` instructs the thread to sleep for 1 second. + +### Submitting tasks to the queue without futures + +Usually, it is best to submit a task to the queue using `submit()`. This allows you to wait for the task to finish and/or get its return value later. However, sometimes a future is not needed, for example when you just want to "set and forget" a certain task, or if the task already communicates with the main thread or with other tasks without using futures, such as via condition variables. In such cases, you may wish to avoid the overhead involved in assigning a future to the task in order to increase performance. + +The member function `push_task()` allows you to submit a task to the queue without generating a future for it. The task can have any number of arguments, but it cannot have a return value. For example: + +```cpp +// Submit a task without arguments or return value to the queue. +pool.push_task(task); +// Submit a task with one argument and no return value to the queue. +pool.push_task(task, arg); +// Submit a task with two arguments and no return value to the queue. +pool.push_task(task, arg1, arg2); +``` + +**Warning:** Since `push_task()` does not return a future, there is no built-in way for the user to know when the task finishes executing. You must use either `wait_for_tasks()` (see below), or some other method such as condition variables, to ensure that the task finishes executing before trying to use anything that depends on its output. Otherwise, bad things will happen! + +### Manually waiting for all tasks to complete + +To wait for a **single** submitted task to complete, use `submit()` and then use the `wait()` or `get()` member functions of the obtained future. However, in cases where you need to wait until **all** submitted tasks finish their execution, or if the tasks have been submitted without futures using `push_task()`, you can use the member function `wait_for_tasks()`. + +Consider, for example, the following code: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool(5); + int squares[100]; + for (int i = 0; i < 100; ++i) + pool.push_task( + [&squares, i] + { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + squares[i] = i * i; + }); + std::cout << squares[50]; +} +``` + +The output will most likely be garbage, since the task that modifies `squares[50]` has not yet finished executing by the time we try to access that element - it's still waiting in the queue. One solution would be to use `submit()` instead of `push_task()`, but perhaps we don't want the overhead of generating 100 different futures. Instead, simply adding the line + +```cpp +pool.wait_for_tasks(); +``` + +after the `for` loop will ensure - as efficiently as possible - that all tasks have finished running before we attempt to access any elements of the array `squares`, and the code will print out the value `2500` as expected. + +Note, however, that `wait_for_tasks()` will wait for **all** the tasks in the queue, including those that are unrelated to the `for` loop. Using [`parallelize_loop()`](#parallelizing-loops) would make much more sense in this particular case, as it will allow waiting only for the tasks related to the loop. + +**Warning:** Never call `wait_for_tasks()` from within a thread of the same thread pool, for example `pool.push_task([] { pool.wait_for_tasks(); })`, as that will cause it to wait forever! + +### Waiting with a timeout + +Sometimes you may wish to wait for the tasks to complete, but only for a certain amount of time, or until a specific point in time. For example, if the tasks have not yet completed after some time, you may wish to let the user know that there is a delay. This can be achieved using two member functions: + +* `wait_for_tasks_duration()` waits for the tasks to be completed, but stops waiting after the specified duration, given as an argument of type `std::chrono::duration`, has passed. +* `wait_for_tasks_until()` waits for the tasks to be completed, but stops waiting after the specified time point, given as an argument of type `std::chrono::time_point`, has been reached. + +Here is an example: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool; + std::atomic done = false; + pool.push_task( + [&done] + { + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + done = true; + }); + while (true) + { + pool.wait_for_tasks_duration(std::chrono::milliseconds(200)); + if (!done) + std::cout << "Sorry, task is not done yet.\n"; + else + break; + } + std::cout << "Task done!\n"; +} +``` + +The output is: + +```none +Sorry, task is not done yet. +Sorry, task is not done yet. +Sorry, task is not done yet. +Sorry, task is not done yet. +Task done! +``` + +### Submitting class member functions to the queue + +Consider the following program: + +```cpp +#include "BS_thread_pool.hpp" + +BS::thread_pool pool; + +class flag_class +{ +public: + bool get_flag() const + { + return flag; + } + + void set_flag(const bool arg) + { + flag = arg; + } + +private: + bool flag = false; +}; + +int main() +{ + flag_class flag_object; + flag_object.set_flag(true); + std::cout << std::boolalpha << flag_object.get_flag() << '\n'; +} +``` + +This program creates a new object `flag_object` of the class `flag_class`, sets the flag to `true` using the member function `set_flag()`, and then prints out the flag's value. But what if you want to submit the member function `set_flag()` as a task to the thread pool? + +To submit member functions to the pool, use the following general syntax: + +```cpp +pool.push_task(&class::function, &object, args); +``` + +The same syntax also works with `submit()`. Note that, in the second argument, you must specify the object on which the member function will be executed (unless it's a static member function, in which case you just submit it like any other function). Also note that both the first and second arguments must be **pointers**, so they must be preceded by the `&` operator. + +If you remove the `&` from the first argument, the code won't work with most compilers, and if you remove the `&` from the second argument, the function will act on a **copy** of the object, rather than on the object itself, so any changes made to the object will not be saved. Therefore, it's important to ensure that both arguments are pointers. + +To make the above program submit the member function `set_flag()` to the thread pool, simply replace the line: + +```cpp +flag_object.set_flag(true); +``` + +with: + +```cpp +pool.push_task(&flag_class::set_flag, &flag_object, true); +pool.wait_for_tasks(); +``` + +Here the class is `flag_class`, the name of the function is `set_flag`, the object we want the function to act on is `flag_object`, and the argument to pass to the function is `true`. + +Another thing you might want to do is call a member function from within the object itself, that is, from another member function. This follows a similar syntax, except that you don't need to specify the class, and you use `this` to get a pointer to the current object (no `&` necessary, since `this` is already a pointer). Here is an example, this time using `submit()`: + +```cpp +#include "BS_thread_pool.hpp" + +BS::thread_pool pool; + +class flag_class +{ +public: + bool get_flag() const + { + return flag; + } + + void set_flag(const bool arg) + { + flag = arg; + } + + void set_flag_to_true() + { + pool.submit(&flag_class::set_flag, this, true).wait(); + } + +private: + bool flag = false; +}; + +int main() +{ + flag_class flag_object; + flag_object.set_flag_to_true(); + std::cout << std::boolalpha << flag_object.get_flag() << '\n'; +} +``` + +### Passing task arguments by reference + +In C++, it is often crucial to pass function arguments by reference or constant reference, instead of by value. This allows the function to access the object being passed directly, rather than creating a new copy of the object. + +When submitting a task using `push_task()` or `submit()`, the task's arguments are always passed by value by default. To pass arguments to the task by reference, you must wrap them with `std::ref()`. Similarly, to pass arguments by constant reference, you must wrap them with `std::cref()`. Here is an example: + +```cpp +#include "BS_thread_pool.hpp" + +BS::thread_pool pool; + +void increment(int& x) +{ + ++x; +} + +void print(const int& x) +{ + std::cout << x; +} + +int main() +{ + int n = 0; + pool.submit(increment, std::ref(n)).wait(); + pool.submit(print, std::cref(n)).wait(); +} +``` + +The `increment()` function takes a **reference** to an integer, and increments that integer. Passing the argument by reference guarantees that `n` itself, in the scope of `main()`, will be incremented - rather than a copy of it in the scope of `increment()`. To pass `n` by reference, we wrapped it inside `std::ref()`. Note that the program will not compile otherwise, since `increment()` only accepts arguments by reference. + +Similarly, the `print()` function takes a **constant reference** to an integer, and prints that integer. Passing the argument by constant reference guarantees that the variable will not be accidentally modified by the function, even though we are accessing `n` itself, rather than a copy. To pass `n` by constant reference, we wrapped it inside `std::cref()`. + +## Parallelizing loops + +### Automatic parallelization of loops + +One of the most common and effective methods of parallelization is splitting a loop into smaller loops and running them in parallel. It is most effective in "embarrassingly parallel" computations, such as vector or matrix operations, where each iteration of the loop is completely independent of every other iteration. For example, if we are summing up two vectors of 1000 elements each, and we have 10 threads, we could split the summation into 10 blocks of 100 elements each, and run all the blocks in parallel, potentially increasing performance by up to a factor of 10. + +`BS::thread_pool` can automatically parallelize loops. To see how this works, consider the following generic loop: + +```cpp +for (T i = start; i < end; ++i) + do_something(i); +``` + +where: + +* `T` is any signed or unsigned integer type. +* The loop is over the range `[start, end)`, i.e. inclusive of `start` but exclusive of `end`. +* `do_something()` is an operation performed for each loop index `i`, such as modifying an array with `end - start` elements. + +This loop may be automatically parallelized and submitted to the thread pool's queue using the member function `parallelize_loop()`, which has the follows syntax: + +```cpp +pool.parallelize_loop(start, end, loop, num_blocks); +``` + +where: + +* `start` is the first index in the range. + * This argument can be omitted, in which case it is assumed that the loop starts at 0. That is, `parallelize_loop(end, loop, num_blocks)` is equivalent to `parallelize_loop(0, end, loop, num_blocks)`. +* `end` is the index after the last index in the range, such that the full range is `[start, end)`. In other words, the loop will be equivalent to the one above if `start` and `end` are the same. + * `start` and `end` should both be integers, but they need not be of the same integer type. `parallelize_loop()` will automatically determine the best type to use for the loop indices. +* `loop()` is any function that takes two indices, `a`, and `b`, and executes only the portion of the loop in the range `[a, b)`. Typically, `loop()` will include a `for` loop of the form `for (T i = a; i < b; ++i)`. +* `num_blocks` is the number of blocks of the form `[a, b)` to split the loop into. For example, if the range is `[0, 9)` and there are 3 blocks, then the blocks will be the ranges `[0, 3)`, `[3, 6)`, and `[6, 9)`. If possible, the blocks will be equal in size; otherwise, the last block may be a bit longer. + * This argument can be omitted, in which case the number of blocks will be the number of threads in the pool. + +Each block will be submitted to the thread pool's queue as a separate task. Therefore, a loop that is split into 3 blocks will be split into 3 individual tasks, which may run in parallel. If there is only one block, then the entire loop will run as one task, and no parallelization will take place. + +To parallelize the generic loop above, we use the following code: + +```cpp +auto loop = [](const T a, const T b) +{ + for (T i = a; i < b; ++i) + do_something(i); +}; +BS::multi_future loop_future = pool.parallelize_loop(start, end, loop, num_blocks); +loop_future.wait(); +``` + +Here we defined `loop()` as a lambda function. Of course, `loop()` could also be defined as a lambda within the call to `parallelize_loop()` itself, as in the examples below; or it could be any ordinary function, but a lambda is preferred since one typically would like to capture some of the surrounding variables. + +`parallelize_loop()` returns an object of the helper class template `BS::multi_future`. Each of the `num_blocks` blocks will have an `std::future` assigned to it, and all these futures will be stored inside the returned `BS::multi_future` object. When `loop_future.wait()` is called, the main thread will wait until all tasks generated by `parallelize_loop()` finish executing, and only those tasks - not any other tasks that also happen to be in the queue. This is essentially the role of the `BS::multi_future` class: to wait for a specific **group of tasks**, in this case the tasks running the loop blocks. + +What value should you use for `num_blocks`? Omitting this argument, so that the number of blocks will be equal to the number of threads in the pool, is typically a good choice. For best performance, it is recommended to do your own benchmarks to find the optimal number of blocks for each loop (you can use the `BS::timer` helper class - see [below](#measuring-execution-time)). Using less tasks than there are threads may be preferred if you are also running other tasks in parallel. Using more tasks than there are threads may improve performance in some cases. + +As a simple example, the following code calculates and prints the squares of all integers from 0 to 99: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + int squares[100]; + for (int i = 0; i < 100; ++i) + { + squares[i] = i * i; + std::cout << i << "^2 = " << squares[i] << " "; + } +} +``` + +We can parallelize it as follows: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool(10); + int squares[100]; + pool.parallelize_loop(100, + [&squares](const int a, const int b) + { + for (int i = a; i < b; ++i) + squares[i] = i * i; + }) + .wait(); + for (int i = 0; i < 100; ++i) + std::cout << i << "^2 = " << squares[i] << " "; +} +``` + +Since there are 10 threads, and we omitted the `num_blocks` argument, the loop will be divided into 10 blocks, each calculating 10 squares. Also, since the loop starts from 0, we did not need to specify the first index. + +In this example, instead of storing the `BS::multi_future` object and then using it to wait, we simply called the `wait()` member function directly on the temporary object returned by `parallelize_loop()` without storing it anywhere. This is a convenient shortcut when we have nothing else to do while waiting. + +Notice that here we parallelized the calculation of the squares, but we did not parallelize printing the results. This is for two reasons: + +1. We want to print out the squares in ascending order, and we have no guarantee that the blocks will be executed in the correct order. This is very important; you must never expect that the parallelized loop will execute at the same order as the non-parallelized loop. +2. If we did print out the squares from within the parallel tasks, we would get a huge mess, since all 10 blocks would print to the standard output at once. [Later](#synchronizing-printing-to-an-output-stream) we will see how to synchronize printing to a stream from multiple tasks at the same time. + +### Loops with return values + +Usually, `parallelize_loop()` should take functions with no return values. This is because the function will be executed once for each block, but the blocks are managed by the thread pool, so there's limited usability in returning one value per block. However, for the case where this is desired, such as for summation or some sorting algorithms, `parallelize_loop()` does accept functions with return values, in which case it returns a `BS::multi_future` object where `T` is the type of the return values. + +Here's an example of summing all the numbers from 1 to 100: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool; + BS::multi_future mf = pool.parallelize_loop(1, 101, + [](const int a, const int b) + { + int block_total = 0; + for (int i = a; i < b; ++i) + block_total += i; + return block_total; + }); + std::vector totals = mf.get(); + int sum = 0; + for (const int t : totals) + sum += t; + std::cout << sum; +} +``` + +Calling `get()` on a `BS::multi_future` object returns an `std::vector` with the values obtained from each future. In this case, the values will be the partial sums from each block, so when we add them up, we will get the total sum. + +### Parallelizing loops without futures + +Just as in the case of [`push_task()`](#submitting-tasks-to-the-queue-without-futures) vs. [`submit()`](#submitting-tasks-to-the-queue-with-futures), sometimes you may want to parallelize a loop, but you don't need it to return a `BS::multi_future`. In this case, you can save the overhead of generating the futures (which can be significant, depending on the number of blocks) by using `push_loop()` instead of `parallelize_loop()`, with the same arguments. + +For example, you could also run the loop of squares example above as follows: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool(10); + int squares[100]; + pool.push_loop(100, + [&squares](const int a, const int b) + { + for (int i = a; i < b; ++i) + squares[i] = i * i; + }); + pool.wait_for_tasks(); + for (int i = 0; i < 100; ++i) + std::cout << i << "^2 = " << squares[i] << " "; +} +``` + +As with `parallelize_loop()`, the first argument can be omitted if the start index is 0, and the last argument can be omitted if the number of blocks should be equal to the number of threads. + +**Warning:** Since `push_loop()` does not return a `BS::multi_future`, there is no built-in way for the user to know when the loop finishes executing. You must use either [`wait_for_tasks()`](#manually-waiting-for-all-tasks-to-complete), or some other method such as condition variables, to ensure that the loop finishes executing before trying to use anything that depends on its output. Otherwise, bad things will happen! + +## Helper classes + +### Synchronizing printing to an output stream + +When printing to an output stream from multiple threads in parallel, the output may become garbled. For example, consider this code: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::thread_pool pool; + for (size_t i = 1; i <= 5; ++i) + pool.push_task([i] { std::cout << "Task no. " << i << " executing.\n"; }); +} +``` + +The output may look as follows: + +```none +Task no. Task no. 2Task no. 5 executing. +Task no. executing. +Task no. 1 executing. +4 executing. +3 executing. +``` + +The reason is that, although each **individual** insertion to `std::cout` is thread-safe, there is no mechanism in place to ensure subsequent insertions from the same thread are printed contiguously. + +The helper class `BS::synced_stream` is designed to eliminate such synchronization issues. The constructor takes one optional argument, specifying the output stream to print to. If no argument is supplied, `std::cout` will be used: + +```cpp +// Construct a synced stream that will print to std::cout. +BS::synced_stream sync_out; +// Construct a synced stream that will print to the output stream my_stream. +BS::synced_stream sync_out(my_stream); +``` + +The member function `print()` takes an arbitrary number of arguments, which are inserted into the stream one by one, in the order they were given. `println()` does the same, but also prints a newline character `\n` at the end, for convenience. A mutex is used to synchronize this process, so that any other calls to `print()` or `println()` using the same `BS::synced_stream` object must wait until the previous call has finished. + +As an example, this code: + +```cpp +#include "BS_thread_pool.hpp" + +int main() +{ + BS::synced_stream sync_out; + BS::thread_pool pool; + for (size_t i = 1; i <= 5; ++i) + pool.push_task([i, &sync_out] { sync_out.println("Task no. ", i, " executing."); }); +} +``` + +Will print out: + +```none +Task no. 1 executing. +Task no. 2 executing. +Task no. 3 executing. +Task no. 4 executing. +Task no. 5 executing. +``` + +**Warning:** Always create the `BS::synced_stream` object **before** the `BS::thread_pool` object, as we did in this example. When the `BS::thread_pool` object goes out of scope, it waits for the remaining tasks to be executed. If the `BS::synced_stream` object goes out of scope before the `BS::thread_pool` object, then any tasks using the `BS::synced_stream` will crash. Since objects are destructed in the opposite order of construction, creating the `BS::synced_stream` object before the `BS::thread_pool` object ensures that the `BS::synced_stream` is always available to the tasks, even while the pool is destructing. + +Most stream manipulators defined in the headers `` and ``, such as `std::setw` (set the character width of the next output), `std::setprecision` (set the precision of floating point numbers), and `std::fixed` (display floating point numbers with a fixed number of digits), can be passed to `print()` and `println()` just as you would pass them to a stream. + +The only exceptions are the flushing manipulators `std::endl` and `std::flush`, which will not work because the compiler will not be able to figure out which template specializations to use. Instead, use `BS::synced_stream::endl` and `BS::synced_stream::flush`. Here is an example: + +```cpp +#include "BS_thread_pool.hpp" +#include +#include + +int main() +{ + BS::synced_stream sync_out; + BS::thread_pool pool; + sync_out.print(std::setprecision(10), std::fixed); + for (size_t i = 1; i <= 10; ++i) + pool.push_task([i, &sync_out] { sync_out.print("The square root of ", std::setw(2), i, " is ", std::sqrt(i), ".", BS::synced_stream::endl); }); +} +``` + +### Handling multiple futures at once + +The helper class template `BS::multi_future`, already introduced in the context of `parallelize_loop()`, provides a convenient way to collect and access groups of futures. This class works similarly to STL containers such as `std::vector`: + +* When you create a new object, either use the default constructor to create an empty object and add futures to it later, or pass the desired number of futures to the constructor in advance. +* Use the `[]` operator to access the future at a specific index, or the `push_back()` member function to append a new future to the list. +* The `size()` member function tells you how many futures are currently stored in the object. +* Once all the futures are stored, you can use `wait()` to wait for all of them at once or `get()` to get an `std::vector` with the results from all of them. + +Aside from using `BS::multi_future` to track the execution of parallelized loops, it can also be used whenever you have several different groups of tasks and you want to track the execution of each group individually. Here's a simple example: + +```cpp +#include "BS_thread_pool.hpp" +#include + +BS::synced_stream sync_out; +BS::thread_pool pool; + +double power(const double i, const double j) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(10 * pool.get_thread_count())); + return std::pow(i, j); +} + +void print_vector(const std::vector& vec) +{ + for (const double i : vec) + sync_out.print(i, ' '); + sync_out.println(); +} + +int main() +{ + constexpr size_t n = 100; + + // First group of tasks: calculate n squares. + // Here we create an empty BS::multi_future object, and append futures to it via push_back(). + BS::multi_future mf_squares; + for (int i = 0; i < n; ++i) + mf_squares.push_back(pool.submit(power, i, 2)); + + // Second group of tasks: calculate n cubes. + // In this case, we create a BS::multi_future object of the desired size in advance, and store the futures via the [] operator. This is faster since there will be no memory reallocations, but also more prone to errors. + BS::multi_future mf_cubes(n); + for (int i = 0; i < n; ++i) + mf_cubes[i] = pool.submit(power, i, 3); + + // Both groups are now queued, but it will take some time until they all execute. + + /// ... + /// Do some stuff while the first group of tasks executes... + /// ... + + // Get and print the results from the first group. + sync_out.println("Squares:"); + print_vector(mf_squares.get()); + + /// ... + /// Do other stuff while the second group of tasks executes... + /// ... + + // Get and print the results from the second group. + sync_out.println("Cubes:"); + print_vector(mf_cubes.get()); +} +``` + +In this example, we simulate complicated tasks by having each task wait for a bit before returning its result. We collect the futures of the tasks submitted within each group into two separate `BS::multi_future` objects. `mf_squares` holds the results from the first group, and `mf_cubes` holds the results from the second group. Now we can wait for and/or get the results from `mf_squares` whenever is convenient, and separately wait for and/or get the results from `mf_cubes` at another time. + +### Measuring execution time + +If you are using a thread pool, then your code is most likely performance-critical. Achieving maximum performance requires performing a considerable amount of benchmarking to determine the optimal settings and algorithms. Therefore, it is important to be able to measure the execution time of various computations and operations under different conditions. + +The helper class `BS::timer` provides a simple way to measure execution time. It is very straightforward to use: + +1. Create a new `BS::timer` object. +2. Immediately before you execute the computation that you want to time, call the `start()` member function. +3. Immediately after the computation ends, call the `stop()` member function. +4. Use the member function `ms()` to obtain the elapsed time for the computation in milliseconds. + +For example: + +```cpp +BS::timer tmr; +tmr.start(); +do_something(); +tmr.stop(); +std::cout << "The elapsed time was " << tmr.ms() << " ms.\n"; +``` + +A practical application of the `BS::timer` class can be found in the benchmark portion of the test program `BS_thread_pool_test.cpp`. + +## Other features + +### Monitoring the tasks + +Sometimes you may wish to monitor what is happening with the tasks you submitted to the pool. This may be done using three member functions: + +* `get_tasks_queued()` gets the number of tasks currently waiting in the queue to be executed by the threads. +* `get_tasks_running()` gets the number of tasks currently being executed by the threads. +* `get_tasks_total()` gets the total number of unfinished tasks: either still in the queue, or running in a thread. +* Note that `get_tasks_total() == get_tasks_queued() + get_tasks_running()`. + +These functions are demonstrated in the following program: + +```cpp +#include "BS_thread_pool.hpp" + +BS::synced_stream sync_out; +BS::thread_pool pool(4); + +void sleep_half_second(const size_t i) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + sync_out.println("Task ", i, " done."); +} + +void monitor_tasks() +{ + sync_out.println(pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued."); +} + +int main() +{ + for (size_t i = 0; i < 12; ++i) + pool.push_task(sleep_half_second, i); + monitor_tasks(); + std::this_thread::sleep_for(std::chrono::milliseconds(750)); + monitor_tasks(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + monitor_tasks(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + monitor_tasks(); +} +``` + +Assuming you have at least 4 hardware threads (so that 4 tasks can run concurrently), the output should be similar to: + +```none +12 tasks total, 0 tasks running, 12 tasks queued. +Task 0 done. +Task 1 done. +Task 2 done. +Task 3 done. +8 tasks total, 4 tasks running, 4 tasks queued. +Task 4 done. +Task 5 done. +Task 6 done. +Task 7 done. +4 tasks total, 4 tasks running, 0 tasks queued. +Task 8 done. +Task 9 done. +Task 10 done. +Task 11 done. +0 tasks total, 0 tasks running, 0 tasks queued. +``` + +### Pausing the workers + +Sometimes you may wish to temporarily pause the execution of tasks, or perhaps you want to submit tasks to the queue in advance and only start executing them at a later time. You can do this using the member functions `pause()`, `unpause()`, and `is_paused()`. + +When you call `pause()`, the workers will temporarily stop retrieving new tasks out of the queue. However, any tasks already executed will keep running until they are done, since the thread pool has no control over the internal code of your tasks. If you need to pause a task in the middle of its execution, you must do that manually by programming your own pause mechanism into the task itself. To resume retrieving tasks, call `unpause()`. To check whether the pool is currently paused, call `is_paused()`. + +Here is an example: + +```cpp +#include "BS_thread_pool.hpp" + +BS::synced_stream sync_out; +BS::thread_pool pool(4); + +void sleep_half_second(const size_t i) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + sync_out.println("Task ", i, " done."); +} + +void check_if_paused() +{ + if (pool.is_paused()) + sync_out.println("Pool paused."); + else + sync_out.println("Pool unpaused."); +} + +int main() +{ + for (size_t i = 0; i < 8; ++i) + pool.push_task(sleep_half_second, i); + sync_out.println("Submitted 8 tasks."); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + pool.pause(); + check_if_paused(); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + sync_out.println("Still paused..."); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + for (size_t i = 8; i < 12; ++i) + pool.push_task(sleep_half_second, i); + sync_out.println("Submitted 4 more tasks."); + sync_out.println("Still paused..."); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + pool.unpause(); + check_if_paused(); +} +``` + +Assuming you have at least 4 hardware threads, the output should be similar to: + +```none +Submitted 8 tasks. +Pool paused. +Task 0 done. +Task 1 done. +Task 2 done. +Task 3 done. +Still paused... +Submitted 4 more tasks. +Still paused... +Pool unpaused. +Task 4 done. +Task 5 done. +Task 6 done. +Task 7 done. +Task 8 done. +Task 9 done. +Task 10 done. +Task 11 done. +``` + +Here is what happened. We initially submitted a total of 8 tasks to the queue. Since we waited for 250ms before pausing, the first 4 tasks have already started running, so they kept running until they finished. While the pool was paused, we submitted 4 more tasks to the queue, but they just waited at the end of the queue. When we unpaused, the remaining 4 initial tasks were executed, followed by the 4 new tasks. + +While the workers are paused, `wait_for_tasks()` will wait for the running tasks instead of all tasks (otherwise it would wait forever). This is demonstrated by the following program: + +```cpp +#include "BS_thread_pool.hpp" + +BS::synced_stream sync_out; +BS::thread_pool pool(4); + +void sleep_half_second(const size_t i) +{ + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + sync_out.println("Task ", i, " done."); +} + +void check_if_paused() +{ + if (pool.is_paused()) + sync_out.println("Pool paused."); + else + sync_out.println("Pool unpaused."); +} + +int main() +{ + for (size_t i = 0; i < 8; ++i) + pool.push_task(sleep_half_second, i); + sync_out.println("Submitted 8 tasks. Waiting for them to complete."); + pool.wait_for_tasks(); + for (size_t i = 8; i < 20; ++i) + pool.push_task(sleep_half_second, i); + sync_out.println("Submitted 12 more tasks."); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + pool.pause(); + check_if_paused(); + sync_out.println("Waiting for the ", pool.get_tasks_running(), " running tasks to complete."); + pool.wait_for_tasks(); + sync_out.println("All running tasks completed. ", pool.get_tasks_queued(), " tasks still queued."); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + sync_out.println("Still paused..."); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + sync_out.println("Still paused..."); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + pool.unpause(); + check_if_paused(); + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + sync_out.println("Waiting for the remaining ", pool.get_tasks_total(), " tasks (", pool.get_tasks_running(), " running and ", pool.get_tasks_queued(), " queued) to complete."); + pool.wait_for_tasks(); + sync_out.println("All tasks completed."); +} +``` + +The output should be similar to: + +```none +Submitted 8 tasks. Waiting for them to complete. +Task 0 done. +Task 1 done. +Task 2 done. +Task 3 done. +Task 4 done. +Task 5 done. +Task 6 done. +Task 7 done. +Submitted 12 more tasks. +Pool paused. +Waiting for the 4 running tasks to complete. +Task 8 done. +Task 9 done. +Task 10 done. +Task 11 done. +All running tasks completed. 8 tasks still queued. +Still paused... +Still paused... +Pool unpaused. +Waiting for the remaining 8 tasks (4 running and 4 queued) to complete. +Task 12 done. +Task 13 done. +Task 14 done. +Task 15 done. +Task 16 done. +Task 17 done. +Task 18 done. +Task 19 done. +All tasks completed. +``` + +The first `wait_for_tasks()`, which was called while the pool was not paused, waited for all 8 tasks, both running and queued. The second `wait_for_tasks()`, which was called after pausing the pool, only waited for the 4 running tasks, while the other 8 tasks remained queued, and were not executed since the pool was paused. Finally, the third `wait_for_tasks()`, which was called after unpausing the pool, waited for the remaining 8 tasks, both running and queued. + +**Warning:** If the thread pool is destroyed while paused, any tasks still in the queue will never be executed! + +### Purging tasks + +Consider a situation where the user cancels a multi-threaded operation while it is still ongoing. Perhaps the operation was split into multiple tasks, and half of the tasks are currently being executed by the pool's threads, but the other half are still waiting in the queue. + +The thread pool cannot terminate the tasks that are already running, as the C++17 standard does not provide that functionality (and in any case, abruptly terminating a task while it's running could have extremely bad consequences, such as memory leaks and data corruption). However, the tasks that are still waiting in the queue can be purged using the `purge()` member function. + +Once `purge()` is called, any tasks still waiting in the queue will be discarded, and will never be executed by the threads. Please note that there is no way to restore the purged tasks; they are gone forever. + +Consider for example the following program: + +```cpp +#include "BS_thread_pool.hpp" + +BS::synced_stream sync_out; +BS::thread_pool pool(4); + +int main() +{ + for (size_t i = 0; i < 8; ++i) + { + pool.push_task( + [i] + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + sync_out.println("Task ", i, " done."); + }); + } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + pool.purge(); + pool.wait_for_tasks(); +} +``` + +The program submit 8 tasks to the queue. Each task waits 100 milliseconds and then prints a message. The thread pool has 4 threads, so it will execute the first 4 tasks in parallel, and then the remaining 4. We wait 50 milliseconds, to ensure that the first 4 tasks have all started running. Then we call `purge()` to purge the remaining 4 tasks. As a result, these tasks never get executed. However, since the first 4 tasks are still running when `purge()` is called, they will finish uninterrupted; `purge()` only discards tasks that have not yet started running. The output of the program therefore only contains the messages from the first 4 tasks: + +```none +Task 0 done. +Task 1 done. +Task 2 done. +Task 3 done. +``` + +### Exception handling + +`submit()` catches any exceptions thrown by the submitted task and forwards them to the corresponding future. They can then be caught when invoking the `get()` member function of the future. For example: + +```cpp +#include "BS_thread_pool.hpp" + +BS::synced_stream sync_out; +BS::thread_pool pool; + +double inverse(const double x) +{ + if (x == 0) + throw std::runtime_error("Division by zero!"); + else + return 1 / x; +} + +int main() +{ + constexpr double num = 0; + std::future my_future = pool.submit(inverse, num); + try + { + const double result = my_future.get(); + sync_out.println("The inverse of ", num, " is ", result, "."); + } + catch (const std::exception& e) + { + sync_out.println("Caught exception: ", e.what()); + } +} +``` + +The output will be: + +```none +Caught exception: Division by zero! +``` + +However, if you change `num` to any non-zero number, no exceptions will be thrown and the inverse will be printed. + +It is important to note that `wait()` does not throw any exceptions; only `get()` does. Therefore, even if your task does not return anything, i.e. your future is an `std::future`, you must still use `get()` on the future obtained from it if you want to catch exceptions thrown by it. Here is an example: + +```cpp +#include "BS_thread_pool.hpp" + +BS::synced_stream sync_out; +BS::thread_pool pool; + +void print_inverse(const double x) +{ + if (x == 0) + throw std::runtime_error("Division by zero!"); + else + sync_out.println("The inverse of ", x, " is ", 1 / x, "."); +} + +int main() +{ + constexpr double num = 0; + std::future my_future = pool.submit(print_inverse, num); + try + { + my_future.get(); + } + catch (const std::exception& e) + { + sync_out.println("Caught exception: ", e.what()); + } +} +``` + +When using `BS::multi_future` to handle multiple futures at once, exception handling works the same way: if any of the futures may throw exceptions, you may catch these exceptions when calling `get()`, even in the case of `BS::multi_future`. + +## Testing the package + +### Automated tests + +The file `BS_thread_pool_test.cpp` in the `tests` folder of the GitHub repository will perform automated tests of all aspects of the package. The output will be printed both to `std::cout` and to a file named `BS_thread_pool_test-yyyy-mm-dd_hh.mm.ss.log` based on the current date and time. In addition, the code is thoroughly documented, and is meant to serve as an extensive example of how to properly use the package. + +Please make sure to: + +1. [Compile](#compiling-and-compatibility) `BS_thread_pool_test.cpp` with optimization flags enabled (e.g. `-O3` on GCC / Clang or `/O2` on MSVC). +2. Run the test without any other applications, especially multithreaded applications, running in parallel. + +A PowerShell script, `BS_thread_pool_test.ps1`, is provided for your convenience in the `tests` folder to make running the test on multiple compilers and operating systems easier. Since it is written in PowerShell, it is fully portable and works on Windows, Linux, and macOS. The script will automatically detect if Clang, GCC, and/or MSVC are available, compile the test program using each available compiler, and then run each compiled test program 5 times and report on any errors. + +If any of the tests fail, please [submit a bug report](https://github.com/bshoshany/thread-pool/issues) including the exact specifications of your system (OS, CPU, compiler, etc.) and the generated log file. + +### Performance tests + +If all checks passed, `BS_thread_pool_test.cpp` performs simple benchmarks by filling a specific number of vectors of fixed size with values. The program decides how many vectors to use, and of what size, by testing how many are needed to reach a certain target duration in a single-threaded computation. This ensures that the test takes approximately the same amount of time on all systems, and is thus more consistent and portable. + +Once the appropriate number and size of vectors has been determined, the program allocates the vectors and fills them with values, calculated according to a fixed prescription. This operation is performed both single-threaded and multithreaded, with the multithreaded computation spread across multiple tasks submitted to the pool. + +Several different multithreaded tests are performed, with the number of tasks either equal to, smaller than, or larger than the pool's thread count. Each test is repeated multiple times, with the run times averaged over all runs of the same test. The run times of the tests are compared, and the maximum speedup obtained is calculated. + +As an example, here are the results of the benchmarks from a [Digital Research Alliance of Canada](https://alliancecan.ca/en) node equipped with two 20-core / 40-thread Intel Xeon Gold 6148 CPUs (for a total of 40 cores and 80 threads), running CentOS Linux 7.9.2009. The tests were compiled using GCC v12.2.0 with the `-O3` and `-march=native` flags. The output was as follows: + +```none +====================== +Performing benchmarks: +====================== +Using 80 threads. +Each test will be repeated 20 times to collect reliable statistics. +Determining the number and size of vectors to generate in order to achieve an approximate mean execution time of 50 ms with 80 tasks... +Generating 3840 vectors with 5120 elements each: +Single-threaded, mean execution time was 2122.5 ms with standard deviation 18.1 ms. +With 20 tasks, mean execution time was 119.8 ms with standard deviation 13.3 ms. +With 40 tasks, mean execution time was 68.9 ms with standard deviation 0.3 ms. +With 80 tasks, mean execution time was 45.5 ms with standard deviation 6.2 ms. +With 160 tasks, mean execution time was 39.3 ms with standard deviation 3.3 ms. +With 320 tasks, mean execution time was 40.4 ms with standard deviation 2.4 ms. +Maximum speedup obtained by multithreading vs. single-threading: 53.9x, using 160 tasks. + ++++++++++++++++++++++++++++++++++++++++ +Thread pool performance test completed! ++++++++++++++++++++++++++++++++++++++++ +``` + +These two CPUs have 40 physical cores in total, with each core providing two separate logical cores via hyperthreading, for a total of 80 threads. Without hyperthreading, we would expect a maximum theoretical speedup of 40x. With hyperthreading, one might naively expect to achieve up to an 80x speedup, but this is in fact impossible, as each pair of hyperthreaded logical cores share the same physical core's resources. However, generally we would expect at most an estimated 30% additional speedup from hyperthreading, which amounts to around 52x in this case. The speedup of 53.9x in our performance test exceeds this estimate. + +## The light version of the package + +This package started out as a very lightweight C++ thread pool, but over time has expanded to include many additional features and helper classes. Therefore, I have decided to bundle a light version of the thread pool in a separate and stand-alone header file, `BS_thread_pool_light.hpp`. + +This file does not contain any of the helper classes, only a new `BS::thread_pool_light` class, which is a minimal thread pool with only the 5 most basic member functions: + +* `get_thread_count()` +* `push_loop()` +* `push_task()` +* `submit()` +* `wait_for_tasks()` + +Note that each header file is 100% stand-alone. If you wish to use the full package, you only need `BS_thread_pool.hpp`, and if you wish to use the light version, you only need `BS_thread_pool_light.hpp`. Only a single header file needs to be included in your project. However, if you wish to use both the light and non-light thread pool classes in the same project, you can include both header files. + +The test program `BS_thread_pool_test.cpp` tests both the full and the light versions of the package. If needed, the current version of the light thread pool can be obtained using the macro `BS_THREAD_POOL_LIGHT_VERSION`. + +## About the project + +### Issue and pull request policy + +This package is under continuous and active development. If you encounter any bugs, or if you would like to request any additional features, please feel free to [open a new issue on GitHub](https://github.com/bshoshany/thread-pool/issues) and I will look into it as soon as I can. + +Contributions are always welcome. However, I release my projects in cumulative updates after editing and testing them locally on my system, so my policy is not to accept any pull requests. If you open a pull request, and I decide to incorporate your suggestion into the project, I will first modify your code to comply with the project's coding conventions (formatting, syntax, naming, comments, programming practices, etc.), and perform some tests to ensure that the change doesn't break anything. I will then merge it into the next release of the project, possibly together with some other changes. The new release will also include a note in `CHANGELOG.md` with a link to your pull request, and modifications to the documentation in `README.md` as needed. + +### Acknowledgements + +Many GitHub users have helped improve this project, directly or indirectly, via issues, pull requests, comments, and/or personal correspondence. Please see `CHANGELOG.md` for links to specific issues and pull requests that have been the most helpful. Thank you all for your contribution! :) + +### Starring the repository + +If you found this project useful, please consider [starring it on GitHub](https://github.com/bshoshany/thread-pool/stargazers)! This allows me to see how many people are using my code, and motivates me to keep working to improve it. + +### Copyright and citing + +Copyright (c) 2023 [Barak Shoshany](http://baraksh.com). Licensed under the [MIT license](LICENSE.txt). + +If you use this C++ thread pool library in software of any kind, please provide a link to [the GitHub repository](https://github.com/bshoshany/thread-pool) in the source code and documentation. + +If you use this library in published research, please cite it as follows: + +* Barak Shoshany, *"A C++17 Thread Pool for High-Performance Scientific Computing"*, [doi:10.5281/zenodo.4742687](https://doi.org/10.5281/zenodo.4742687), [arXiv:2105.00613](https://arxiv.org/abs/2105.00613) (May 2021) + +You can use the following BibTeX entry: + +```none +@article{Shoshany2021_ThreadPool, + archiveprefix = {arXiv}, + author = {Barak Shoshany}, + doi = {10.5281/zenodo.4742687}, + eid = {arXiv:2105.00613}, + eprint = {2105.00613}, + journal = {arXiv e-prints}, + keywords = {Computer Science - Distributed, Parallel, and Cluster Computing, D.1.3, D.1.5}, + month = {May}, + primaryclass = {cs.DC}, + title = {{A C++17 Thread Pool for High-Performance Scientific Computing}}, + year = {2021} +} +``` + +Please note that the [companion paper on arXiv](https://arxiv.org/abs/2105.00613) is updated infrequently. The paper is intended to facilitate discovery of the package by scientists who may find it useful for scientific computing purposes and to allow citing the package in scientific research, but most users should read the `README.md` file on [the GitHub repository](https://github.com/bshoshany/thread-pool) instead, as it is guaranteed to always be up to date. + +### Learning more about C++ + +Beginner C++ programmers may be interested in [my lecture notes](https://baraksh.com/CSE701/notes.php) for a course taught at McMaster University, which teach modern C and C++ from scratch, including some of the advanced techniques and programming practices used in developing this library. diff --git a/external/include/common/thread-pool-3.5.0/include/BS_thread_pool.hpp b/external/include/common/thread-pool-3.5.0/include/BS_thread_pool.hpp new file mode 100644 index 000000000..cb6fe084a --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/include/BS_thread_pool.hpp @@ -0,0 +1,819 @@ +#pragma once + +/** + * @file BS_thread_pool.hpp + * @author Barak Shoshany (baraksh@gmail.com) (http://baraksh.com) + * @version 3.5.0 + * @date 2023-05-25 + * @copyright Copyright (c) 2023 Barak Shoshany. Licensed under the MIT license. If you found this project useful, please consider starring it on GitHub! If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021) + * + * @brief BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library. This header file contains the entire library, including the main BS::thread_pool class and the helper classes BS::multi_future, BS::blocks, BS:synced_stream, and BS::timer. + */ + +#define BS_THREAD_POOL_VERSION "v3.5.0 (2023-05-25)" + +#include // std::chrono +#include // std::condition_variable +#include // std::current_exception +#include // std::bind, std::function, std::invoke +#include // std::future, std::promise +#include // std::cout, std::endl, std::flush, std::ostream +#include // std::make_shared, std::make_unique, std::shared_ptr, std::unique_ptr +#include // std::mutex, std::scoped_lock, std::unique_lock +#include // std::queue +#include // std::thread +#include // std::common_type_t, std::conditional_t, std::decay_t, std::invoke_result_t, std::is_void_v +#include // std::forward, std::move, std::swap +#include // std::vector + +namespace BS +{ +/** + * @brief A convenient shorthand for the type of std::thread::hardware_concurrency(). Should evaluate to unsigned int. + */ +using concurrency_t = std::invoke_result_t; + +// ============================================================================================= // +// Begin class multi_future // + +/** + * @brief A helper class to facilitate waiting for and/or getting the results of multiple futures at once. + * + * @tparam T The return type of the futures. + */ +template +class [[nodiscard]] multi_future +{ +public: + /** + * @brief Construct a multi_future object with the given number of futures. + * + * @param num_futures_ The desired number of futures to store. + */ + multi_future(const size_t num_futures_ = 0) : futures(num_futures_) {} + + /** + * @brief Get the results from all the futures stored in this multi_future object, rethrowing any stored exceptions. + * + * @return If the futures return void, this function returns void as well. Otherwise, it returns a vector containing the results. + */ + [[nodiscard]] std::conditional_t, void, std::vector> get() + { + if constexpr (std::is_void_v) + { + for (size_t i = 0; i < futures.size(); ++i) + futures[i].get(); + return; + } + else + { + std::vector results(futures.size()); + for (size_t i = 0; i < futures.size(); ++i) + results[i] = futures[i].get(); + return results; + } + } + + /** + * @brief Get a reference to one of the futures stored in this multi_future object. + * + * @param i The index of the desired future. + * @return The future. + */ + [[nodiscard]] std::future& operator[](const size_t i) + { + return futures[i]; + } + + /** + * @brief Append a future to this multi_future object. + * + * @param future The future to append. + */ + void push_back(std::future future) + { + futures.push_back(std::move(future)); + } + + /** + * @brief Get the number of futures stored in this multi_future object. + * + * @return The number of futures. + */ + [[nodiscard]] size_t size() const + { + return futures.size(); + } + + /** + * @brief Wait for all the futures stored in this multi_future object. + */ + void wait() const + { + for (size_t i = 0; i < futures.size(); ++i) + futures[i].wait(); + } + +private: + /** + * @brief A vector to store the futures. + */ + std::vector> futures; +}; + +// End class multi_future // +// ============================================================================================= // + +// ============================================================================================= // +// Begin class blocks // + +/** + * @brief A helper class to divide a range into blocks. Used by parallelize_loop() and push_loop(). + * + * @tparam T1 The type of the first index in the range. Should be a signed or unsigned integer. + * @tparam T2 The type of the index after the last index in the range. Should be a signed or unsigned integer. If T1 is not the same as T2, a common type will be automatically inferred. + * @tparam T The common type of T1 and T2. + */ +template > +class [[nodiscard]] blocks +{ +public: + /** + * @brief Construct a blocks object with the given specifications. + * + * @param first_index_ The first index in the range. + * @param index_after_last_ The index after the last index in the range. + * @param num_blocks_ The desired number of blocks to divide the range into. + */ + blocks(const T1 first_index_, const T2 index_after_last_, const size_t num_blocks_) : first_index(static_cast(first_index_)), index_after_last(static_cast(index_after_last_)), num_blocks(num_blocks_) + { + if (index_after_last < first_index) + std::swap(index_after_last, first_index); + total_size = static_cast(index_after_last - first_index); + block_size = static_cast(total_size / num_blocks); + if (block_size == 0) + { + block_size = 1; + num_blocks = (total_size > 1) ? total_size : 1; + } + } + + /** + * @brief Get the first index of a block. + * + * @param i The block number. + * @return The first index. + */ + [[nodiscard]] T start(const size_t i) const + { + return static_cast(i * block_size) + first_index; + } + + /** + * @brief Get the index after the last index of a block. + * + * @param i The block number. + * @return The index after the last index. + */ + [[nodiscard]] T end(const size_t i) const + { + return (i == num_blocks - 1) ? index_after_last : (static_cast((i + 1) * block_size) + first_index); + } + + /** + * @brief Get the number of blocks. Note that this may be different than the desired number of blocks that was passed to the constructor. + * + * @return The number of blocks. + */ + [[nodiscard]] size_t get_num_blocks() const + { + return num_blocks; + } + + /** + * @brief Get the total number of indices in the range. + * + * @return The total number of indices. + */ + [[nodiscard]] size_t get_total_size() const + { + return total_size; + } + +private: + /** + * @brief The size of each block (except possibly the last block). + */ + size_t block_size = 0; + + /** + * @brief The first index in the range. + */ + T first_index = 0; + + /** + * @brief The index after the last index in the range. + */ + T index_after_last = 0; + + /** + * @brief The number of blocks. + */ + size_t num_blocks = 0; + + /** + * @brief The total number of indices in the range. + */ + size_t total_size = 0; +}; + +// End class blocks // +// ============================================================================================= // + +// ============================================================================================= // +// Begin class thread_pool // + +/** + * @brief A fast, lightweight, and easy-to-use C++17 thread pool class. + */ +class [[nodiscard]] thread_pool +{ +public: + // ============================ + // Constructors and destructors + // ============================ + + /** + * @brief Construct a new thread pool. + * + * @param thread_count_ The number of threads to use. The default value is the total number of hardware threads available, as reported by the implementation. This is usually determined by the number of cores in the CPU. If a core is hyperthreaded, it will count as two threads. + */ + thread_pool(const concurrency_t thread_count_ = 0) : thread_count(determine_thread_count(thread_count_)), threads(std::make_unique(determine_thread_count(thread_count_))) + { + create_threads(); + } + + /** + * @brief Destruct the thread pool. Waits for all tasks to complete, then destroys all threads. Note that if the pool is paused, then any tasks still in the queue will never be executed. + */ + ~thread_pool() + { + wait_for_tasks(); + destroy_threads(); + } + + // ======================= + // Public member functions + // ======================= + + /** + * @brief Get the number of tasks currently waiting in the queue to be executed by the threads. + * + * @return The number of queued tasks. + */ + [[nodiscard]] size_t get_tasks_queued() const + { + const std::scoped_lock tasks_lock(tasks_mutex); + return tasks.size(); + } + + /** + * @brief Get the number of tasks currently being executed by the threads. + * + * @return The number of running tasks. + */ + [[nodiscard]] size_t get_tasks_running() const + { + const std::scoped_lock tasks_lock(tasks_mutex); + return tasks_running; + } + + /** + * @brief Get the total number of unfinished tasks: either still waiting in the queue, or running in a thread. Note that get_tasks_total() == get_tasks_queued() + get_tasks_running(). + * + * @return The total number of tasks. + */ + [[nodiscard]] size_t get_tasks_total() const + { + const std::scoped_lock tasks_lock(tasks_mutex); + return tasks_running + tasks.size(); + } + + /** + * @brief Get the number of threads in the pool. + * + * @return The number of threads. + */ + [[nodiscard]] concurrency_t get_thread_count() const + { + return thread_count; + } + + /** + * @brief Check whether the pool is currently paused. + * + * @return true if the pool is paused, false if it is not paused. + */ + [[nodiscard]] bool is_paused() const + { + const std::scoped_lock tasks_lock(tasks_mutex); + return paused; + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and submitting each block separately to the queue. Returns a multi_future object that contains the futures for all of the blocks. + * + * @tparam F The type of the function to loop through. + * @tparam T1 The type of the first index in the loop. Should be a signed or unsigned integer. + * @tparam T2 The type of the index after the last index in the loop. Should be a signed or unsigned integer. If T1 is not the same as T2, a common type will be automatically inferred. + * @tparam T The common type of T1 and T2. + * @tparam R The return value of the loop function F (can be void). + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The loop will iterate from first_index to (index_after_last - 1) inclusive. In other words, it will be equivalent to "for (T i = first_index; i < index_after_last; ++i)". Note that if index_after_last == first_index, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per block. Should take exactly two arguments: the first index in the block and the index after the last index in the block. loop(start, end) should typically involve a loop of the form "for (T i = start; i < end; ++i)". + * @param num_blocks The maximum number of blocks to split the loop into. The default is to use the number of threads in the pool. + * @return A multi_future object that can be used to wait for all the blocks to finish. If the loop function returns a value, the multi_future object can also be used to obtain the values returned by each block. + */ + template , typename R = std::invoke_result_t, T, T>> + [[nodiscard]] multi_future parallelize_loop(const T1 first_index, const T2 index_after_last, F&& loop, const size_t num_blocks = 0) + { + blocks blks(first_index, index_after_last, num_blocks ? num_blocks : thread_count); + if (blks.get_total_size() > 0) + { + multi_future mf(blks.get_num_blocks()); + for (size_t i = 0; i < blks.get_num_blocks(); ++i) + mf[i] = submit(std::forward(loop), blks.start(i), blks.end(i)); + return mf; + } + else + { + return multi_future(); + } + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and submitting each block separately to the queue. Returns a multi_future object that contains the futures for all of the blocks. This overload is used for the special case where the first index is 0. + * + * @tparam F The type of the function to loop through. + * @tparam T The type of the loop indices. Should be a signed or unsigned integer. + * @tparam R The return value of the loop function F (can be void). + * @param index_after_last The index after the last index in the loop. The loop will iterate from 0 to (index_after_last - 1) inclusive. In other words, it will be equivalent to "for (T i = 0; i < index_after_last; ++i)". Note that if index_after_last == 0, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per block. Should take exactly two arguments: the first index in the block and the index after the last index in the block. loop(start, end) should typically involve a loop of the form "for (T i = start; i < end; ++i)". + * @param num_blocks The maximum number of blocks to split the loop into. The default is to use the number of threads in the pool. + * @return A multi_future object that can be used to wait for all the blocks to finish. If the loop function returns a value, the multi_future object can also be used to obtain the values returned by each block. + */ + template , T, T>> + [[nodiscard]] multi_future parallelize_loop(const T index_after_last, F&& loop, const size_t num_blocks = 0) + { + return parallelize_loop(0, index_after_last, std::forward(loop), num_blocks); + } + + /** + * @brief Pause the pool. The workers will temporarily stop retrieving new tasks out of the queue, although any tasks already executed will keep running until they are finished. + */ + void pause() + { + const std::scoped_lock tasks_lock(tasks_mutex); + paused = true; + } + + /** + * @brief Purge all the tasks waiting in the queue. Tasks that are currently running will not be affected, but any tasks still waiting in the queue will be discarded, and will never be executed by the threads. Please note that there is no way to restore the purged tasks. + */ + void purge() + { + const std::scoped_lock tasks_lock(tasks_mutex); + while (!tasks.empty()) + tasks.pop(); + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and submitting each block separately to the queue. Does not return a multi_future, so the user must use wait_for_tasks() or some other method to ensure that the loop finishes executing, otherwise bad things will happen. + * + * @tparam F The type of the function to loop through. + * @tparam T1 The type of the first index in the loop. Should be a signed or unsigned integer. + * @tparam T2 The type of the index after the last index in the loop. Should be a signed or unsigned integer. If T1 is not the same as T2, a common type will be automatically inferred. + * @tparam T The common type of T1 and T2. + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The loop will iterate from first_index to (index_after_last - 1) inclusive. In other words, it will be equivalent to "for (T i = first_index; i < index_after_last; ++i)". Note that if index_after_last == first_index, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per block. Should take exactly two arguments: the first index in the block and the index after the last index in the block. loop(start, end) should typically involve a loop of the form "for (T i = start; i < end; ++i)". + * @param num_blocks The maximum number of blocks to split the loop into. The default is to use the number of threads in the pool. + */ + template > + void push_loop(const T1 first_index, const T2 index_after_last, F&& loop, const size_t num_blocks = 0) + { + blocks blks(first_index, index_after_last, num_blocks ? num_blocks : thread_count); + if (blks.get_total_size() > 0) + { + for (size_t i = 0; i < blks.get_num_blocks(); ++i) + push_task(std::forward(loop), blks.start(i), blks.end(i)); + } + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and submitting each block separately to the queue. Does not return a multi_future, so the user must use wait_for_tasks() or some other method to ensure that the loop finishes executing, otherwise bad things will happen. This overload is used for the special case where the first index is 0. + * + * @tparam F The type of the function to loop through. + * @tparam T The type of the loop indices. Should be a signed or unsigned integer. + * @param index_after_last The index after the last index in the loop. The loop will iterate from 0 to (index_after_last - 1) inclusive. In other words, it will be equivalent to "for (T i = 0; i < index_after_last; ++i)". Note that if index_after_last == 0, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per block. Should take exactly two arguments: the first index in the block and the index after the last index in the block. loop(start, end) should typically involve a loop of the form "for (T i = start; i < end; ++i)". + * @param num_blocks The maximum number of blocks to split the loop into. The default is to use the number of threads in the pool. + */ + template + void push_loop(const T index_after_last, F&& loop, const size_t num_blocks = 0) + { + push_loop(0, index_after_last, std::forward(loop), num_blocks); + } + + /** + * @brief Push a function with zero or more arguments, but no return value, into the task queue. Does not return a future, so the user must use wait_for_tasks() or some other method to ensure that the task finishes executing, otherwise bad things will happen. + * + * @tparam F The type of the function. + * @tparam A The types of the arguments. + * @param task The function to push. + * @param args The zero or more arguments to pass to the function. Note that if the task is a class member function, the first argument must be a pointer to the object, i.e. &object (or this), followed by the actual arguments. + */ + template + void push_task(F&& task, A&&... args) + { + { + const std::scoped_lock tasks_lock(tasks_mutex); + tasks.push(std::bind(std::forward(task), std::forward(args)...)); // cppcheck-suppress ignoredReturnValue + } + task_available_cv.notify_one(); + } + + /** + * @brief Reset the number of threads in the pool. Waits for all currently running tasks to be completed, then destroys all threads in the pool and creates a new thread pool with the new number of threads. Any tasks that were waiting in the queue before the pool was reset will then be executed by the new threads. If the pool was paused before resetting it, the new pool will be paused as well. + * + * @param thread_count_ The number of threads to use. The default value is the total number of hardware threads available, as reported by the implementation. This is usually determined by the number of cores in the CPU. If a core is hyperthreaded, it will count as two threads. + */ + void reset(const concurrency_t thread_count_ = 0) + { + std::unique_lock tasks_lock(tasks_mutex); + const bool was_paused = paused; + paused = true; + tasks_lock.unlock(); + wait_for_tasks(); + destroy_threads(); + thread_count = determine_thread_count(thread_count_); + threads = std::make_unique(thread_count); + paused = was_paused; + create_threads(); + } + + /** + * @brief Submit a function with zero or more arguments into the task queue. If the function has a return value, get a future for the eventual returned value. If the function has no return value, get an std::future which can be used to wait until the task finishes. + * + * @tparam F The type of the function. + * @tparam A The types of the zero or more arguments to pass to the function. + * @tparam R The return type of the function (can be void). + * @param task The function to submit. + * @param args The zero or more arguments to pass to the function. Note that if the task is a class member function, the first argument must be a pointer to the object, i.e. &object (or this), followed by the actual arguments. + * @return A future to be used later to wait for the function to finish executing and/or obtain its returned value if it has one. + */ + template , std::decay_t...>> + [[nodiscard]] std::future submit(F&& task, A&&... args) + { + std::shared_ptr> task_promise = std::make_shared>(); + push_task( + [task_function = std::bind(std::forward(task), std::forward(args)...), task_promise] + { + try + { + if constexpr (std::is_void_v) + { + std::invoke(task_function); + task_promise->set_value(); + } + else + { + task_promise->set_value(std::invoke(task_function)); + } + } + catch (...) + { + try + { + task_promise->set_exception(std::current_exception()); + } + catch (...) + { + } + } + }); + return task_promise->get_future(); + } + + /** + * @brief Unpause the pool. The workers will resume retrieving new tasks out of the queue. + */ + void unpause() + { + const std::scoped_lock tasks_lock(tasks_mutex); + paused = false; + } + + /** + * @brief Wait for tasks to be completed. Normally, this function waits for all tasks, both those that are currently running in the threads and those that are still waiting in the queue. However, if the pool is paused, this function only waits for the currently running tasks (otherwise it would wait forever). Note: To wait for just one specific task, use submit() instead, and call the wait() member function of the generated future. + */ + void wait_for_tasks() + { + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + tasks_done_cv.wait(tasks_lock, [this] { return !tasks_running && (paused || tasks.empty()); }); + waiting = false; + } + + /** + * @brief Wait for tasks to be completed, but stop waiting after the specified duration has passed. + * + * @tparam R An arithmetic type representing the number of ticks to wait. + * @tparam P An std::ratio representing the length of each tick in seconds. + * @param duration The time duration to wait. + * @return true if all tasks finished running, false if the duration expired but some tasks are still running. + */ + template + bool wait_for_tasks_duration(const std::chrono::duration& duration) + { + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + const bool status = tasks_done_cv.wait_for(tasks_lock, duration, [this] { return !tasks_running && (paused || tasks.empty()); }); + waiting = false; + return status; + } + + /** + * @brief Wait for tasks to be completed, but stop waiting after the specified time point has been reached. + * + * @tparam C The type of the clock used to measure time. + * @tparam D An std::chrono::duration type used to indicate the time point. + * @param timeout_time The time point at which to stop waiting. + * @return true if all tasks finished running, false if the time point was reached but some tasks are still running. + */ + template + bool wait_for_tasks_until(const std::chrono::time_point& timeout_time) + { + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + const bool status = tasks_done_cv.wait_until(tasks_lock, timeout_time, [this] { return !tasks_running && (paused || tasks.empty()); }); + waiting = false; + return status; + } + +private: + // ======================== + // Private member functions + // ======================== + + /** + * @brief Create the threads in the pool and assign a worker to each thread. + */ + void create_threads() + { + { + const std::scoped_lock tasks_lock(tasks_mutex); + workers_running = true; + } + for (concurrency_t i = 0; i < thread_count; ++i) + { + threads[i] = std::thread(&thread_pool::worker, this); + } + } + + /** + * @brief Destroy the threads in the pool. + */ + void destroy_threads() + { + { + const std::scoped_lock tasks_lock(tasks_mutex); + workers_running = false; + } + task_available_cv.notify_all(); + for (concurrency_t i = 0; i < thread_count; ++i) + { + threads[i].join(); + } + } + + /** + * @brief Determine how many threads the pool should have, based on the parameter passed to the constructor or reset(). + * + * @param thread_count_ The parameter passed to the constructor or reset(). If the parameter is a positive number, then the pool will be created with this number of threads. If the parameter is non-positive, or a parameter was not supplied (in which case it will have the default value of 0), then the pool will be created with the total number of hardware threads available, as obtained from std::thread::hardware_concurrency(). If the latter returns a non-positive number for some reason, then the pool will be created with just one thread. + * @return The number of threads to use for constructing the pool. + */ + [[nodiscard]] concurrency_t determine_thread_count(const concurrency_t thread_count_) const + { + if (thread_count_ > 0) + return thread_count_; + else + { + if (std::thread::hardware_concurrency() > 0) + return std::thread::hardware_concurrency(); + else + return 1; + } + } + + /** + * @brief A worker function to be assigned to each thread in the pool. Waits until it is notified by push_task() that a task is available, and then retrieves the task from the queue and executes it. Once the task finishes, the worker notifies wait_for_tasks() in case it is waiting. + */ + void worker() + { + std::function task; + while (true) + { + std::unique_lock tasks_lock(tasks_mutex); + task_available_cv.wait(tasks_lock, [this] { return !tasks.empty() || !workers_running; }); + if (!workers_running) + break; + if (paused) + continue; + task = std::move(tasks.front()); + tasks.pop(); + ++tasks_running; + tasks_lock.unlock(); + task(); + tasks_lock.lock(); + --tasks_running; + if (waiting && !tasks_running && (paused || tasks.empty())) + tasks_done_cv.notify_all(); + } + } + + // ============ + // Private data + // ============ + + /** + * @brief A flag indicating whether the workers should pause. When set to true, the workers temporarily stop retrieving new tasks out of the queue, although any tasks already executed will keep running until they are finished. When set to false again, the workers resume retrieving tasks. + */ + bool paused = false; + + /** + * @brief A condition variable to notify worker() that a new task has become available. + */ + std::condition_variable task_available_cv = {}; + + /** + * @brief A condition variable to notify wait_for_tasks() that the tasks are done. + */ + std::condition_variable tasks_done_cv = {}; + + /** + * @brief A queue of tasks to be executed by the threads. + */ + std::queue> tasks = {}; + + /** + * @brief A counter for the total number of currently running tasks. + */ + size_t tasks_running = 0; + + /** + * @brief A mutex to synchronize access to the task queue by different threads. + */ + mutable std::mutex tasks_mutex = {}; + + /** + * @brief The number of threads in the pool. + */ + concurrency_t thread_count = 0; + + /** + * @brief A smart pointer to manage the memory allocated for the threads. + */ + std::unique_ptr threads = nullptr; + + /** + * @brief A flag indicating that wait_for_tasks() is active and expects to be notified whenever a task is done. + */ + bool waiting = false; + + /** + * @brief A flag indicating to the workers to keep running. When set to false, the workers terminate permanently. + */ + bool workers_running = false; +}; + +// End class thread_pool // +// ============================================================================================= // + +// ============================================================================================= // +// Begin class synced_stream // + +/** + * @brief A helper class to synchronize printing to an output stream by different threads. + */ +class [[nodiscard]] synced_stream +{ +public: + /** + * @brief Construct a new synced stream. + * + * @param out_stream_ The output stream to print to. The default value is std::cout. + */ + synced_stream(std::ostream& out_stream_ = std::cout) : out_stream(out_stream_) {} + + /** + * @brief Print any number of items into the output stream. Ensures that no other threads print to this stream simultaneously, as long as they all exclusively use the same synced_stream object to print. + * + * @tparam T The types of the items + * @param items The items to print. + */ + template + void print(T&&... items) + { + const std::scoped_lock lock(stream_mutex); + (out_stream << ... << std::forward(items)); + } + + /** + * @brief Print any number of items into the output stream, followed by a newline character. Ensures that no other threads print to this stream simultaneously, as long as they all exclusively use the same synced_stream object to print. + * + * @tparam T The types of the items + * @param items The items to print. + */ + template + void println(T&&... items) + { + print(std::forward(items)..., '\n'); + } + + /** + * @brief A stream manipulator to pass to a synced_stream (an explicit cast of std::endl). Prints a newline character to the stream, and then flushes it. Should only be used if flushing is desired, otherwise '\n' should be used instead. + */ + inline static std::ostream& (&endl)(std::ostream&) = static_cast(std::endl); + + /** + * @brief A stream manipulator to pass to a synced_stream (an explicit cast of std::flush). Used to flush the stream. + */ + inline static std::ostream& (&flush)(std::ostream&) = static_cast(std::flush); + +private: + /** + * @brief The output stream to print to. + */ + std::ostream& out_stream; + + /** + * @brief A mutex to synchronize printing. + */ + mutable std::mutex stream_mutex = {}; +}; + +// End class synced_stream // +// ============================================================================================= // + +// ============================================================================================= // +// Begin class timer // + +/** + * @brief A helper class to measure execution time for benchmarking purposes. + */ +class [[nodiscard]] timer +{ +public: + /** + * @brief Start (or restart) measuring time. + */ + void start() + { + start_time = std::chrono::steady_clock::now(); + } + + /** + * @brief Stop measuring time and store the elapsed time since start(). + */ + void stop() + { + elapsed_time = std::chrono::steady_clock::now() - start_time; + } + + /** + * @brief Get the number of milliseconds that have elapsed between start() and stop(). + * + * @return The number of milliseconds. + */ + [[nodiscard]] std::chrono::milliseconds::rep ms() const + { + return (std::chrono::duration_cast(elapsed_time)).count(); + } + +private: + /** + * @brief The time point when measuring started. + */ + std::chrono::time_point start_time = std::chrono::steady_clock::now(); + + /** + * @brief The duration that has elapsed between start() and stop(). + */ + std::chrono::duration elapsed_time = std::chrono::duration::zero(); +}; + +// End class timer // +// ============================================================================================= // + +} // namespace BS diff --git a/external/include/common/thread-pool-3.5.0/include/BS_thread_pool_light.hpp b/external/include/common/thread-pool-3.5.0/include/BS_thread_pool_light.hpp new file mode 100644 index 000000000..4ff2a24f1 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/include/BS_thread_pool_light.hpp @@ -0,0 +1,327 @@ +#pragma once + +/** + * @file BS_thread_pool_light.hpp + * @author Barak Shoshany (baraksh@gmail.com) (http://baraksh.com) + * @version 3.5.0 + * @date 2023-05-25 + * @copyright Copyright (c) 2023 Barak Shoshany. Licensed under the MIT license. If you found this project useful, please consider starring it on GitHub! If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021) + * + * @brief BS::thread_pool_light: a fast, lightweight, and easy-to-use C++17 thread pool library. This header file contains a light version of the main library, for use when advanced features are not needed. + */ + +#define BS_THREAD_POOL_LIGHT_VERSION "v3.5.0 (2023-05-25)" + +#include // std::condition_variable +#include // std::current_exception +#include // std::bind, std::function, std::invoke +#include // std::future, std::promise +#include // std::make_shared, std::make_unique, std::shared_ptr, std::unique_ptr +#include // std::mutex, std::scoped_lock, std::unique_lock +#include // std::queue +#include // std::thread +#include // std::common_type_t, std::decay_t, std::invoke_result_t, std::is_void_v +#include // std::forward, std::move, std::swap + +namespace BS +{ +/** + * @brief A convenient shorthand for the type of std::thread::hardware_concurrency(). Should evaluate to unsigned int. + */ +using concurrency_t = std::invoke_result_t; + +/** + * @brief A fast, lightweight, and easy-to-use C++17 thread pool class. This is a lighter version of the main thread pool class. + */ +class [[nodiscard]] thread_pool_light +{ +public: + // ============================ + // Constructors and destructors + // ============================ + + /** + * @brief Construct a new thread pool. + * + * @param thread_count_ The number of threads to use. The default value is the total number of hardware threads available, as reported by the implementation. This is usually determined by the number of cores in the CPU. If a core is hyperthreaded, it will count as two threads. + */ + thread_pool_light(const concurrency_t thread_count_ = 0) : thread_count(determine_thread_count(thread_count_)), threads(std::make_unique(determine_thread_count(thread_count_))) + { + create_threads(); + } + + /** + * @brief Destruct the thread pool. Waits for all tasks to complete, then destroys all threads. + */ + ~thread_pool_light() + { + wait_for_tasks(); + destroy_threads(); + } + + // ======================= + // Public member functions + // ======================= + + /** + * @brief Get the number of threads in the pool. + * + * @return The number of threads. + */ + [[nodiscard]] concurrency_t get_thread_count() const + { + return thread_count; + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and submitting each block separately to the queue. The user must use wait_for_tasks() or some other method to ensure that the loop finishes executing, otherwise bad things will happen. + * + * @tparam F The type of the function to loop through. + * @tparam T1 The type of the first index in the loop. Should be a signed or unsigned integer. + * @tparam T2 The type of the index after the last index in the loop. Should be a signed or unsigned integer. If T1 is not the same as T2, a common type will be automatically inferred. + * @tparam T The common type of T1 and T2. + * @param first_index The first index in the loop. + * @param index_after_last The index after the last index in the loop. The loop will iterate from first_index to (index_after_last - 1) inclusive. In other words, it will be equivalent to "for (T i = first_index; i < index_after_last; ++i)". Note that if index_after_last == first_index, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per block. Should take exactly two arguments: the first index in the block and the index after the last index in the block. loop(start, end) should typically involve a loop of the form "for (T i = start; i < end; ++i)". + * @param num_blocks The maximum number of blocks to split the loop into. The default is to use the number of threads in the pool. + */ + template > + void push_loop(T1 first_index_, T2 index_after_last_, F&& loop, size_t num_blocks = 0) + { + T first_index = static_cast(first_index_); + T index_after_last = static_cast(index_after_last_); + if (num_blocks == 0) + num_blocks = thread_count; + if (index_after_last < first_index) + std::swap(index_after_last, first_index); + size_t total_size = static_cast(index_after_last - first_index); + size_t block_size = static_cast(total_size / num_blocks); + if (block_size == 0) + { + block_size = 1; + num_blocks = (total_size > 1) ? total_size : 1; + } + if (total_size > 0) + { + for (size_t i = 0; i < num_blocks; ++i) + push_task(std::forward(loop), static_cast(i * block_size) + first_index, (i == num_blocks - 1) ? index_after_last : (static_cast((i + 1) * block_size) + first_index)); + } + } + + /** + * @brief Parallelize a loop by automatically splitting it into blocks and submitting each block separately to the queue. The user must use wait_for_tasks() or some other method to ensure that the loop finishes executing, otherwise bad things will happen. This overload is used for the special case where the first index is 0. + * + * @tparam F The type of the function to loop through. + * @tparam T The type of the loop indices. Should be a signed or unsigned integer. + * @param index_after_last The index after the last index in the loop. The loop will iterate from 0 to (index_after_last - 1) inclusive. In other words, it will be equivalent to "for (T i = 0; i < index_after_last; ++i)". Note that if index_after_last == 0, no blocks will be submitted. + * @param loop The function to loop through. Will be called once per block. Should take exactly two arguments: the first index in the block and the index after the last index in the block. loop(start, end) should typically involve a loop of the form "for (T i = start; i < end; ++i)". + * @param num_blocks The maximum number of blocks to split the loop into. The default is to use the number of threads in the pool. + */ + template + void push_loop(const T index_after_last, F&& loop, const size_t num_blocks = 0) + { + push_loop(0, index_after_last, std::forward(loop), num_blocks); + } + + /** + * @brief Push a function with zero or more arguments, but no return value, into the task queue. Does not return a future, so the user must use wait_for_tasks() or some other method to ensure that the task finishes executing, otherwise bad things will happen. + * + * @tparam F The type of the function. + * @tparam A The types of the arguments. + * @param task The function to push. + * @param args The zero or more arguments to pass to the function. Note that if the task is a class member function, the first argument must be a pointer to the object, i.e. &object (or this), followed by the actual arguments. + */ + template + void push_task(F&& task, A&&... args) + { + { + const std::scoped_lock tasks_lock(tasks_mutex); + tasks.push(std::bind(std::forward(task), std::forward(args)...)); // cppcheck-suppress ignoredReturnValue + } + task_available_cv.notify_one(); + } + + /** + * @brief Submit a function with zero or more arguments into the task queue. If the function has a return value, get a future for the eventual returned value. If the function has no return value, get an std::future which can be used to wait until the task finishes. + * + * @tparam F The type of the function. + * @tparam A The types of the zero or more arguments to pass to the function. + * @tparam R The return type of the function (can be void). + * @param task The function to submit. + * @param args The zero or more arguments to pass to the function. Note that if the task is a class member function, the first argument must be a pointer to the object, i.e. &object (or this), followed by the actual arguments. + * @return A future to be used later to wait for the function to finish executing and/or obtain its returned value if it has one. + */ + template , std::decay_t...>> + [[nodiscard]] std::future submit(F&& task, A&&... args) + { + std::shared_ptr> task_promise = std::make_shared>(); + push_task( + [task_function = std::bind(std::forward(task), std::forward(args)...), task_promise] + { + try + { + if constexpr (std::is_void_v) + { + std::invoke(task_function); + task_promise->set_value(); + } + else + { + task_promise->set_value(std::invoke(task_function)); + } + } + catch (...) + { + try + { + task_promise->set_exception(std::current_exception()); + } + catch (...) + { + } + } + }); + return task_promise->get_future(); + } + + /** + * @brief Wait for tasks to be completed, both those that are currently running in the threads and those that are still waiting in the queue. Note: To wait for just one specific task, use submit() instead, and call the wait() member function of the generated future. + */ + void wait_for_tasks() + { + std::unique_lock tasks_lock(tasks_mutex); + waiting = true; + tasks_done_cv.wait(tasks_lock, [this] { return !tasks_running && tasks.empty(); }); + waiting = false; + } + +private: + // ======================== + // Private member functions + // ======================== + + /** + * @brief Create the threads in the pool and assign a worker to each thread. + */ + void create_threads() + { + { + const std::scoped_lock tasks_lock(tasks_mutex); + workers_running = true; + } + for (concurrency_t i = 0; i < thread_count; ++i) + { + threads[i] = std::thread(&thread_pool_light::worker, this); + } + } + + /** + * @brief Destroy the threads in the pool. + */ + void destroy_threads() + { + { + const std::scoped_lock tasks_lock(tasks_mutex); + workers_running = false; + } + task_available_cv.notify_all(); + for (concurrency_t i = 0; i < thread_count; ++i) + { + threads[i].join(); + } + } + + /** + * @brief Determine how many threads the pool should have, based on the parameter passed to the constructor. + * + * @param thread_count_ The parameter passed to the constructor. If the parameter is a positive number, then the pool will be created with this number of threads. If the parameter is non-positive, or a parameter was not supplied (in which case it will have the default value of 0), then the pool will be created with the total number of hardware threads available, as obtained from std::thread::hardware_concurrency(). If the latter returns a non-positive number for some reason, then the pool will be created with just one thread. + * @return The number of threads to use for constructing the pool. + */ + [[nodiscard]] concurrency_t determine_thread_count(const concurrency_t thread_count_) const + { + if (thread_count_ > 0) + return thread_count_; + else + { + if (std::thread::hardware_concurrency() > 0) + return std::thread::hardware_concurrency(); + else + return 1; + } + } + + /** + * @brief A worker function to be assigned to each thread in the pool. Waits until it is notified by push_task() that a task is available, and then retrieves the task from the queue and executes it. Once the task finishes, the worker notifies wait_for_tasks() in case it is waiting. + */ + void worker() + { + std::function task; + while (true) + { + std::unique_lock tasks_lock(tasks_mutex); + task_available_cv.wait(tasks_lock, [this] { return !tasks.empty() || !workers_running; }); + if (!workers_running) + break; + task = std::move(tasks.front()); + tasks.pop(); + ++tasks_running; + tasks_lock.unlock(); + task(); + tasks_lock.lock(); + --tasks_running; + if (waiting && !tasks_running && tasks.empty()) + tasks_done_cv.notify_all(); + } + } + + // ============ + // Private data + // ============ + + /** + * @brief A condition variable to notify worker() that a new task has become available. + */ + std::condition_variable task_available_cv = {}; + + /** + * @brief A condition variable to notify wait_for_tasks() that the tasks are done. + */ + std::condition_variable tasks_done_cv = {}; + + /** + * @brief A queue of tasks to be executed by the threads. + */ + std::queue> tasks = {}; + + /** + * @brief A counter for the total number of currently running tasks. + */ + size_t tasks_running = 0; + + /** + * @brief A mutex to synchronize access to the task queue by different threads. + */ + mutable std::mutex tasks_mutex = {}; + + /** + * @brief The number of threads in the pool. + */ + concurrency_t thread_count = 0; + + /** + * @brief A smart pointer to manage the memory allocated for the threads. + */ + std::unique_ptr threads = nullptr; + + /** + * @brief A flag indicating that wait_for_tasks() is active and expects to be notified whenever a task is done. + */ + bool waiting = false; + + /** + * @brief A flag indicating to the workers to keep running. When set to false, the workers terminate permanently. + */ + bool workers_running = false; +}; + +} // namespace BS diff --git a/external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.cpp b/external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.cpp new file mode 100644 index 000000000..0dda7e4f6 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.cpp @@ -0,0 +1,1535 @@ +/** + * @file BS_thread_pool_test.cpp + * @author Barak Shoshany (baraksh@gmail.com) (http://baraksh.com) + * @version 3.5.0 + * @date 2023-05-25 + * @copyright Copyright (c) 2023 Barak Shoshany. Licensed under the MIT license. If you found this project useful, please consider starring it on GitHub! If you use this library in software of any kind, please provide a link to the GitHub repository https://github.com/bshoshany/thread-pool in the source code and documentation. If you use this library in published research, please cite it as follows: Barak Shoshany, "A C++17 Thread Pool for High-Performance Scientific Computing", doi:10.5281/zenodo.4742687, arXiv:2105.00613 (May 2021) + * + * @brief BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library. This program tests all aspects of the library, but is not needed in order to use the library. + */ + +// Get rid of annoying MSVC warning. +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include // std::min, std::min_element, std::sort, std::unique +#include // std::atomic +#include // std::chrono +#include // std::abs, std::cos, std::exp, std::llround, std::log, std::round, std::sin, std::sqrt +#include // std::condition_variable +#include // std::quick_exit +#include // std::localtime, std::strftime, std::time, std::time_t +#include // std::exception +#include // std::ofstream +#include // std::cref, std::ref +#include // std::future +#include // std::setprecision, std::setw +#include // std::fixed +#include // std::cout +#include // std::make_unique, std::unique_ptr +#include // std::mutex, std::scoped_lock, std::unique_lock +#include // std::mt19937_64, std::random_device, std::uniform_int_distribution +#include // std::runtime_error +#include // std::string, std::to_string +#include // std::this_thread, std::thread +#include // std::is_same_v +#include // std::forward, std::pair +#include // std::begin, std::end, std::vector + +// Include the header files for the thread pool library. +#include "../include/BS_thread_pool.hpp" +#include "../include/BS_thread_pool_light.hpp" + +// ================ +// Global variables +// ================ + +// Whether to output to a log file in addition to the standard output. +constexpr bool output_log = true; + +// Whether to perform the tests. +constexpr bool enable_tests = true; + +// Whether to perform the benchmarks. +constexpr bool enable_benchmarks = true; + +// Whether to perform the long deadlock tests. Defaults to false since they can take much longer than the other tests. +constexpr bool enable_long_deadlock_tests = false; + +// A global synced_stream object which prints to std::cout. +BS::synced_stream sync_cout(std::cout); + +// A global stream object used to access the log file. +std::ofstream log_file; + +// A global synced_stream object which prints to the log file. +BS::synced_stream sync_file(log_file); + +// Global thread pool objects to be used throughout the test. +BS::thread_pool pool_full; +BS::thread_pool_light pool_light; + +// A global random_device object to be used to seed some random number generators. +std::random_device rd; + +// A global variable to measure how many checks succeeded. +size_t tests_succeeded = 0; + +// A global variable to measure how many checks failed. +size_t tests_failed = 0; + +// ================ +// Helper functions +// ================ + +/** + * @brief Print any number of items into both std::cout and the log file, syncing both independently. + * + * @tparam T The types of the items. + * @param items The items to print. + */ +template +void dual_print(T&&... items) +{ + sync_cout.print(std::forward(items)...); + if (output_log) + sync_file.print(std::forward(items)...); +} + +/** + * @brief Print any number of items into both std::cout and the log file, syncing both independently. Also prints a newline character, and flushes the stream. + * + * @tparam T The types of the items. + * @param items The items to print. + */ +template +void dual_println(T&&... items) +{ + dual_print(std::forward(items)..., BS::synced_stream::endl); +} + +/** + * @brief Print a stylized header. + * + * @param text The text of the header. Will appear between two lines. + * @param symbol The symbol to use for the lines. Default is '='. + */ +void print_header(const std::string& text, const char symbol = '=') +{ + dual_println(); + dual_println(std::string(text.length(), symbol)); + dual_println(text); + dual_println(std::string(text.length(), symbol)); +} + +/** + * @brief Get a string representing the current time. + * + * @return The string. + */ +std::string get_time() +{ + const std::time_t t = std::time(nullptr); + char time_string[32]; + std::strftime(time_string, sizeof(time_string), "%Y-%m-%d_%H.%M.%S", std::localtime(&t)); + return time_string; +} + +/** + * @brief Check if a condition is met, report the result, and keep count of the total number of successes and failures. + * + * @param condition The condition to check. + */ +void check(const bool condition) +{ + if (condition) + { + dual_println("-> PASSED!"); + ++tests_succeeded; + } + else + { + dual_println("-> FAILED!"); + ++tests_failed; + } +} + +/** + * @brief Check if the expected result has been obtained, report the result, and keep count of the total number of successes and failures. + * + * @param condition The condition to check. + */ +template +void check(const T1 expected, const T2 obtained) +{ + dual_print("Expected: ", expected, ", obtained: ", obtained); + if (expected == obtained) + { + dual_println(" -> PASSED!"); + ++tests_succeeded; + } + else + { + dual_println(" -> FAILED!"); + ++tests_failed; + } +} + +// ========================================= +// Functions to verify the number of threads +// ========================================= + +/** + * @brief Count the number of unique threads in the pool. Submits a number of tasks equal to twice the thread count into the pool. Each task stores the ID of the thread running it, and then waits until released by the main thread. This ensures that each thread in the pool runs at least one task. The number of unique thread IDs is then counted from the stored IDs. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +BS::concurrency_t count_unique_threads(P& pool) +{ + std::condition_variable ID_cv, total_cv; + std::mutex ID_mutex, total_mutex; + { + const BS::concurrency_t num_tasks = pool.get_thread_count() * 2; + std::vector thread_IDs(num_tasks); + std::unique_lock total_lock(total_mutex); + BS::concurrency_t total_count = 0; + bool ID_release = false; + pool.wait_for_tasks(); + for (std::thread::id& id : thread_IDs) + pool.push_task( + [&total_count, &id, &ID_release, &ID_cv, &total_cv, &ID_mutex, &total_mutex] + { + id = std::this_thread::get_id(); + { + const std::scoped_lock total_lock_local(total_mutex); + ++total_count; + } + total_cv.notify_one(); + std::unique_lock ID_lock_local(ID_mutex); + ID_cv.wait(ID_lock_local, [&ID_release] { return ID_release; }); + }); + total_cv.wait(total_lock, [&total_count, &pool] { return total_count == pool.get_thread_count(); }); + { + const std::scoped_lock ID_lock(ID_mutex); + ID_release = true; + } + ID_cv.notify_all(); + total_cv.wait(total_lock, [&total_count, &num_tasks] { return total_count == num_tasks; }); + pool.wait_for_tasks(); + std::sort(thread_IDs.begin(), thread_IDs.end()); + return static_cast(std::unique(thread_IDs.begin(), thread_IDs.end()) - thread_IDs.begin()); + } +} + +/** + * @brief Check that the constructor works. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_constructor(P& pool) +{ + dual_println("Checking that the thread pool reports a number of threads equal to the hardware concurrency..."); + check(std::thread::hardware_concurrency(), pool.get_thread_count()); + dual_println("Checking that the manually counted number of unique thread IDs is equal to the reported number of threads..."); + check(pool.get_thread_count(), count_unique_threads(pool)); +} + +/** + * @brief Check that reset() works. + * + * @param pool The thread pool to check. + */ +void check_reset(BS::thread_pool& pool) +{ + pool.reset(std::thread::hardware_concurrency() / 2); + dual_println("Checking that after reset() the thread pool reports a number of threads equal to half the hardware concurrency..."); + check(std::thread::hardware_concurrency() / 2, pool.get_thread_count()); + dual_println("Checking that after reset() the manually counted number of unique thread IDs is equal to the reported number of threads..."); + check(pool.get_thread_count(), count_unique_threads(pool)); + pool.reset(std::thread::hardware_concurrency()); + dual_println("Checking that after a second reset() the thread pool reports a number of threads equal to the hardware concurrency..."); + check(std::thread::hardware_concurrency(), pool.get_thread_count()); + dual_println("Checking that after a second reset() the manually counted number of unique thread IDs is equal to the reported number of threads..."); + check(pool.get_thread_count(), count_unique_threads(pool)); +} + +// ================================ +// Functions to check for deadlocks +// ================================ + +// An auxiliary thread pool used by check_deadlock(). +BS::thread_pool check_deadlock_pool; + +/** + * @brief Check that the specified function does not create deadlocks. The function will be run many times to increase the probability of encountering a deadlock as a result of subtle timing issues. Uses an auxiliary pool so the whole test doesn't get stuck if a deadlock is encountered. + * + * @tparam F The type of the function. + * @param task The function to try. + */ +template +void check_deadlock(const F&& task) +{ + constexpr uint32_t tries = 10000; + uint32_t i = 0; + check_deadlock_pool.push_task( + [&i, &task] + { + do + task(); + while (++i < tries); + }); + bool passed = false; + while (true) + { + uint32_t old_i = i; + check_deadlock_pool.wait_for_tasks_duration(std::chrono::milliseconds(500)); + if (i == tries) + { + dual_println("Successfully finished all tries!"); + passed = true; + break; + } + else if (i > old_i) + { + dual_println("Finished ", i, " tries out of ", tries, "..."); + } + else + { + dual_println("Error: deadlock detected!"); + passed = false; + break; + } + } + check(passed); +} + +// ======================================= +// Functions to verify submission of tasks +// ======================================= + +/** + * @brief A class to detect when a copy or move constructor has been invoked. + */ +class detect_copy_move +{ +public: + detect_copy_move() = default; + detect_copy_move(const detect_copy_move&) + { + copied = true; + } + detect_copy_move(detect_copy_move&&) + { + moved = true; + } + bool copied = false; + bool moved = false; +}; + +/** + * @brief Check that push_task() works. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_push_task(P& pool) +{ + dual_println("Checking that push_task() works for a function with no arguments or return value..."); + { + bool flag = false; + pool.push_task([&flag] { flag = true; }); + pool.wait_for_tasks(); + check(flag); + } + dual_println("Checking that push_task() works for a function with one argument and no return value..."); + { + bool flag = false; + pool.push_task([](bool* flag_) { *flag_ = true; }, &flag); + pool.wait_for_tasks(); + check(flag); + } + dual_println("Checking that push_task() works for a function with two arguments and no return value..."); + { + bool flag1 = false; + bool flag2 = false; + pool.push_task([](bool* flag1_, bool* flag2_) { *flag1_ = *flag2_ = true; }, &flag1, &flag2); + pool.wait_for_tasks(); + check(flag1 && flag2); + } + dual_println("Checking that push_task() does not create unnecessary copies of the function object..."); + { + bool copied = false; + bool moved = false; + pool.push_task( + [detect = detect_copy_move(), &copied, &moved] + { + copied = detect.copied; + moved = detect.moved; + }); + pool.wait_for_tasks(); + check(!copied && moved); + } + dual_println("Checking that push_task() correctly accepts arguments passed by value, reference, and constant reference..."); + { + int64_t pass_me_by_value = 0; + pool.push_task( + [](int64_t passed_by_value) + { + if (++passed_by_value) + std::this_thread::yield(); + }, + pass_me_by_value); + pool.wait_for_tasks(); + check(pass_me_by_value == 0); + int64_t pass_me_by_ref = 0; + pool.push_task([](int64_t& passed_by_ref) { ++passed_by_ref; }, std::ref(pass_me_by_ref)); + pool.wait_for_tasks(); + check(pass_me_by_ref == 1); + int64_t pass_me_by_cref = 0; + std::atomic release = false; + pool.push_task( + [&release](const int64_t& passed_by_cref) + { + while (!release) + std::this_thread::yield(); + check(passed_by_cref == 1); + }, + std::cref(pass_me_by_cref)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ++pass_me_by_cref; + release = true; + pool.wait_for_tasks(); + } +} + +/** + * @brief Check that submit() works. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_submit(P& pool) +{ + dual_println("Checking that submit() works for a function with no arguments or return value..."); + { + bool flag = false; + pool.submit([&flag] { flag = true; }).wait(); + check(flag); + } + dual_println("Checking that submit() works for a function with one argument and no return value..."); + { + bool flag = false; + pool.submit([](bool* flag_) { *flag_ = true; }, &flag).wait(); + check(flag); + } + dual_println("Checking that submit() works for a function with two arguments and no return value..."); + { + bool flag1 = false; + bool flag2 = false; + pool.submit([](bool* flag1_, bool* flag2_) { *flag1_ = *flag2_ = true; }, &flag1, &flag2).wait(); + check(flag1 && flag2); + } + dual_println("Checking that submit() works for a function with no arguments and a return value..."); + { + bool flag = false; + std::future flag_future = pool.submit( + [&flag] + { + flag = true; + return 42; + }); + check(flag_future.get() == 42 && flag); + } + dual_println("Checking that submit() works for a function with one argument and a return value..."); + { + bool flag = false; + std::future flag_future = pool.submit( + [](bool* flag_) + { + *flag_ = true; + return 42; + }, + &flag); + check(flag_future.get() == 42 && flag); + } + dual_println("Checking that submit() works for a function with two arguments and a return value..."); + { + bool flag1 = false; + bool flag2 = false; + std::future flag_future = pool.submit( + [](bool* flag1_, bool* flag2_) + { + *flag1_ = *flag2_ = true; + return 42; + }, + &flag1, &flag2); + check(flag_future.get() == 42 && flag1 && flag2); + } + dual_println("Checking that submit() does not create unnecessary copies of the function object..."); + { + std::future> fut = pool.submit([detect = detect_copy_move()] { return std::pair(detect.copied, detect.moved); }); + std::pair result = fut.get(); + check(!result.first && result.second); + } + dual_println("Checking that submit() correctly accepts arguments passed by value, reference, and constant reference..."); + { + int64_t pass_me_by_value = 0; + pool.submit( + [](int64_t passed_by_value) + { + if (++passed_by_value) + std::this_thread::yield(); + }, + pass_me_by_value) + .wait(); + check(pass_me_by_value == 0); + int64_t pass_me_by_ref = 0; + pool.submit([](int64_t& passed_by_ref) { ++passed_by_ref; }, std::ref(pass_me_by_ref)).wait(); + check(pass_me_by_ref == 1); + int64_t pass_me_by_cref = 0; + std::atomic release = false; + std::future fut = pool.submit( + [&release](const int64_t& passed_by_cref) + { + while (!release) + std::this_thread::yield(); + check(passed_by_cref == 1); + }, + std::cref(pass_me_by_cref)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + ++pass_me_by_cref; + release = true; + fut.wait(); + } +} + +/** + * @brief A class to facilitate checking that member functions of different types have been successfully submitted. + * + * @tparam P The type of the thread pool to check. + */ +template +class flag_class +{ +public: + flag_class(P& pool_) : pool(pool_) {} + + void set_flag_no_args() + { + flag = true; + } + + void set_flag_one_arg(const bool arg) + { + flag = arg; + } + + int set_flag_no_args_return() + { + flag = true; + return 42; + } + + int set_flag_one_arg_return(const bool arg) + { + flag = arg; + return 42; + } + + [[nodiscard]] bool get_flag() const + { + return flag; + } + + void push_test_flag_no_args() + { + pool.push_task(&flag_class

::set_flag_no_args, this); + pool.wait_for_tasks(); + check(get_flag()); + } + + void push_test_flag_one_arg() + { + pool.push_task(&flag_class

::set_flag_one_arg, this, true); + pool.wait_for_tasks(); + check(get_flag()); + } + + void submit_test_flag_no_args() + { + pool.submit(&flag_class

::set_flag_no_args, this).wait(); + check(get_flag()); + } + + void submit_test_flag_one_arg() + { + pool.submit(&flag_class

::set_flag_one_arg, this, true).wait(); + check(get_flag()); + } + + void submit_test_flag_no_args_return() + { + std::future flag_future = pool.submit(&flag_class

::set_flag_no_args_return, this); + check(flag_future.get() == 42 && get_flag()); + } + + void submit_test_flag_one_arg_return() + { + std::future flag_future = pool.submit(&flag_class

::set_flag_one_arg_return, this, true); + check(flag_future.get() == 42 && get_flag()); + } + +private: + bool flag = false; + P& pool; +}; + +/** + * @brief Check that submitting member functions works. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_member_function(P& pool) +{ + dual_println("Checking that push_task() works for a member function with no arguments or return value..."); + { + flag_class

flag(pool); + pool.push_task(&flag_class

::set_flag_no_args, &flag); + pool.wait_for_tasks(); + check(flag.get_flag()); + } + dual_println("Checking that push_task() works for a member function with one argument and no return value..."); + { + flag_class

flag(pool); + pool.push_task(&flag_class

::set_flag_one_arg, &flag, true); + pool.wait_for_tasks(); + check(flag.get_flag()); + } + dual_println("Checking that submit() works for a member function with no arguments or return value..."); + { + flag_class

flag(pool); + pool.submit(&flag_class

::set_flag_no_args, &flag).wait(); + check(flag.get_flag()); + } + dual_println("Checking that submit() works for a member function with one argument and no return value..."); + { + flag_class

flag(pool); + pool.submit(&flag_class

::set_flag_one_arg, &flag, true).wait(); + check(flag.get_flag()); + } + dual_println("Checking that submit() works for a member function with no arguments and a return value..."); + { + flag_class

flag(pool); + std::future flag_future = pool.submit(&flag_class

::set_flag_no_args_return, &flag); + check(flag_future.get() == 42 && flag.get_flag()); + } + dual_println("Checking that submit() works for a member function with one argument and a return value..."); + { + flag_class

flag(pool); + std::future flag_future = pool.submit(&flag_class

::set_flag_one_arg_return, &flag, true); + check(flag_future.get() == 42 && flag.get_flag()); + } +} + +/** + * @brief Check that submitting member functions within an object works. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_member_function_within_object(P& pool) +{ + dual_println("Checking that push_task() works within an object for a member function with no arguments or return value..."); + { + flag_class

flag(pool); + flag.push_test_flag_no_args(); + } + dual_println("Checking that push_task() works within an object for a member function with one argument and no return value..."); + { + flag_class

flag(pool); + flag.push_test_flag_one_arg(); + } + dual_println("Checking that submit() works within an object for a member function with no arguments or return value..."); + { + flag_class

flag(pool); + flag.submit_test_flag_no_args(); + } + dual_println("Checking that submit() works within an object for a member function with one argument and no return value..."); + { + flag_class

flag(pool); + flag.submit_test_flag_one_arg(); + } + dual_println("Checking that submit() works within an object for a member function with no arguments and a return value..."); + { + flag_class

flag(pool); + flag.submit_test_flag_no_args_return(); + } + dual_println("Checking that submit() works within an object for a member function with one argument and a return value..."); + { + flag_class

flag(pool); + flag.submit_test_flag_one_arg_return(); + } +} + +/** + * @brief Check that wait_for_tasks() works. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_wait_for_tasks(P& pool) +{ + const BS::concurrency_t n = pool.get_thread_count() * 10; + std::unique_ptr[]> flags = std::make_unique[]>(n); + for (BS::concurrency_t i = 0; i < n; ++i) + pool.push_task( + [&flags, i] + { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + flags[i] = true; + }); + dual_println("Waiting for tasks..."); + pool.wait_for_tasks(); + bool all_flags = true; + for (BS::concurrency_t i = 0; i < n; ++i) + all_flags = all_flags && flags[i]; + check(all_flags); +} + +/** + * @brief Check that wait_for_tasks() correctly blocks all external threads that call it. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_wait_for_tasks_blocks(P& pool) +{ + pool.wait_for_tasks(); + dual_println("Checking that wait_for_tasks() correctly blocks all external threads that call it..."); + std::atomic release = false; + pool.push_task( + [&release] + { + dual_println("Task submitted to pool 1 and waiting to be released..."); + while (!release) + std::this_thread::yield(); + dual_println("Task released."); + }); + constexpr BS::concurrency_t num_waiting_tasks = 4; + P temp_pool(num_waiting_tasks); + std::unique_ptr[]> flags = std::make_unique[]>(num_waiting_tasks); + auto waiting_task = [&flags, &pool](const BS::concurrency_t i) + { + dual_println("Task ", i, " submitted to pool 2 and waiting for pool 1's task to finish..."); + pool.wait_for_tasks(); + dual_println("Task ", i, " finished waiting."); + flags[i] = true; + }; + for (BS::concurrency_t i = 0; i < num_waiting_tasks; ++i) + temp_pool.push_task(waiting_task, i); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + bool any_flag_set = false; + for (BS::concurrency_t i = 0; i < num_waiting_tasks; ++i) + any_flag_set = any_flag_set || flags[i]; + check(!any_flag_set); + release = true; + temp_pool.wait_for_tasks(); + bool all_flags_set = true; + for (size_t i = 0; i < num_waiting_tasks; ++i) + all_flags_set = all_flags_set && flags[i]; + check(all_flags_set); +} + +/** + * @brief Check that calling wait_for_tasks() more than once doesn't create a deadlock. Uses a mechanism similar to check_deadlock(). + * + * @tparam P The type of the thread pool to check. + */ +template +void check_wait_for_tasks_deadlock() +{ + dual_println("Checking for deadlocks when waiting for tasks..."); + P temp_pool(1); + temp_pool.push_task([] { std::this_thread::sleep_for(std::chrono::milliseconds(500)); }); + constexpr uint32_t n_waiting_tasks = 1000; + std::atomic count = 0; + for (uint32_t j = 0; j < n_waiting_tasks; ++j) + { + check_deadlock_pool.push_task( + [&temp_pool, &count] + { + temp_pool.wait_for_tasks(); + ++count; + }); + } + bool passed = false; + while (true) + { + uint32_t old_count = count; + check_deadlock_pool.wait_for_tasks_duration(std::chrono::milliseconds(1000)); + if (count == n_waiting_tasks) + { + dual_println("All waiting tasks successfully finished!"); + passed = true; + break; + } + else if (count > old_count) + { + dual_println(count, " tasks out of ", n_waiting_tasks, " finished waiting..."); + } + else + { + dual_println("Error: deadlock detected!"); + passed = false; + break; + } + } + check(passed); +} + +/** + * @brief Check that wait_for_tasks_duration() works. + * + * @param pool The thread pool to check. + */ +void check_wait_for_tasks_duration(BS::thread_pool& pool) +{ + dual_println("Checking that wait_for_tasks_duration() works..."); + pool.reset(); + std::atomic done = false; + pool.push_task( + [&done] + { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + done = true; + }); + dual_println("Task submitted. Waiting for 10ms..."); + pool.wait_for_tasks_duration(std::chrono::milliseconds(10)); + check(!done); + dual_println("Waiting for 500ms..."); + pool.wait_for_tasks_duration(std::chrono::milliseconds(500)); + check(done); +} + +/** + * @brief Check that wait_for_tasks_until() works. + * + * @param pool The thread pool to check. + */ +void check_wait_for_tasks_until(BS::thread_pool& pool) +{ + dual_println("Checking that wait_for_tasks_until() works..."); + pool.reset(); + std::atomic done = false; + pool.push_task( + [&done] + { + std::this_thread::sleep_for(std::chrono::milliseconds(250)); + done = true; + }); + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + dual_println("Task submitted. Waiting until 10ms from submission time..."); + pool.wait_for_tasks_until(now + std::chrono::milliseconds(10)); + check(!done); + dual_println("Waiting until 500ms from submission time..."); + pool.wait_for_tasks_until(now + std::chrono::milliseconds(500)); + check(done); +} + +// ======================================== +// Functions to verify loop parallelization +// ======================================== + +/** + * @brief Check that push_loop() or parallelize_loop() work for a specific range of indices split over a specific number of tasks, with no return value. + * + * @tparam P The type of the thread pool to check. + * @tparam T The type of the last index in the loop plus 1. + * @param pool The thread pool to check. + * @param random_start The first index in the loop. + * @param random_end The last index in the loop plus 1. + * @param num_tasks The number of tasks. + * @param use_push Whether to check push_loop() instead of parallelize_loop(). + */ +template +void check_parallelize_loop_no_return(P& pool, const int64_t random_start, T random_end, const BS::concurrency_t num_tasks, const bool use_push = false) +{ + if (random_start == random_end) + ++random_end; + dual_println("Verifying that ", use_push ? "push_loop()" : "parallelize_loop()", " from ", random_start, " to ", random_end, " with ", num_tasks, num_tasks == 1 ? " task" : " tasks", " modifies all indices..."); + const size_t num_indices = static_cast(std::abs(random_end - random_start)); + const int64_t offset = std::min(random_start, static_cast(random_end)); + std::unique_ptr[]> flags = std::make_unique[]>(num_indices); + const auto loop = [&flags, offset](const int64_t start, const int64_t end) + { + for (int64_t i = start; i < end; ++i) + flags[static_cast(i - offset)] = true; + }; + if (use_push) + { + if (random_start == 0) + pool.push_loop(random_end, loop, num_tasks); + else + pool.push_loop(random_start, random_end, loop, num_tasks); + pool.wait_for_tasks(); + } + else if constexpr (std::is_same_v) + { + if (random_start == 0) + pool.parallelize_loop(random_end, loop, num_tasks).wait(); + else + pool.parallelize_loop(random_start, random_end, loop, num_tasks).wait(); + } + bool all_flags = true; + for (size_t i = 0; i < num_indices; ++i) + all_flags = all_flags && flags[i]; + check(all_flags); +} + +/** + * @brief Check that parallelize_loop() works for a specific range of indices split over a specific number of tasks, with a return value. + * + * @param pool The thread pool to check. + * @param random_start The first index in the loop. + * @param random_end The last index in the loop plus 1. + * @param num_tasks The number of tasks. + */ +void check_parallelize_loop_return(BS::thread_pool& pool, const int64_t random_start, int64_t random_end, const BS::concurrency_t num_tasks) +{ + if (random_start == random_end) + ++random_end; + dual_println("Verifying that parallelize_loop() from ", random_start, " to ", random_end, " with ", num_tasks, num_tasks == 1 ? " task" : " tasks", " correctly sums all indices..."); + const auto loop = [](const int64_t start, const int64_t end) + { + int64_t total = 0; + for (int64_t i = start; i < end; ++i) + total += i; + return total; + }; + const std::vector sums_vector = (random_start == 0) ? pool.parallelize_loop(random_end, loop, num_tasks).get() : pool.parallelize_loop(random_start, random_end, loop, num_tasks).get(); + int64_t sum = 0; + for (const int64_t& s : sums_vector) + sum += s; + check(std::abs(random_start - random_end) * (random_start + random_end - 1), sum * 2); +} + +/** + * @brief Check that push_loop() and parallelize_loop() work using several different random values for the range of indices and number of tasks. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_loop(P& pool) +{ + std::mt19937_64 mt(rd()); + std::uniform_int_distribution index_dist(-1000000, 1000000); + std::uniform_int_distribution task_dist(1, pool.get_thread_count()); + constexpr uint64_t n = 10; + if constexpr (std::is_same_v) + { + for (uint64_t i = 0; i < n; ++i) + check_parallelize_loop_no_return(pool, index_dist(mt), index_dist(mt), task_dist(mt)); + for (uint64_t i = 0; i < n; ++i) + check_parallelize_loop_return(pool, index_dist(mt), index_dist(mt), task_dist(mt)); + dual_println("Verifying that parallelize_loop() with identical start and end indices does nothing..."); + bool flag = true; + const int64_t index = index_dist(mt); + pool.parallelize_loop(index, index, [&flag](const int64_t, const int64_t) { flag = false; }).wait(); + check(flag); + dual_println("Trying parallelize_loop() with start and end indices of different types:"); + const int64_t start = index_dist(mt); + const uint32_t end = static_cast(std::abs(index_dist(mt))); + check_parallelize_loop_no_return(pool, start, end, task_dist(mt)); + dual_println("Trying the overload of parallelize_loop() for the case where the first index is equal to 0:"); + check_parallelize_loop_no_return(pool, 0, index_dist(mt), task_dist(mt)); + check_parallelize_loop_return(pool, 0, index_dist(mt), task_dist(mt)); + } + for (uint64_t i = 0; i < n; ++i) + check_parallelize_loop_no_return(pool, index_dist(mt), index_dist(mt), task_dist(mt), true); + dual_println("Verifying that push_loop() with identical start and end indices does nothing..."); + bool flag = true; + const int64_t index = index_dist(mt); + pool.push_loop(index, index, [&flag](const int64_t, const int64_t) { flag = false; }); + pool.wait_for_tasks(); + check(flag); + dual_println("Trying push_loop() with start and end indices of different types:"); + const int64_t start = index_dist(mt); + const uint32_t end = static_cast(std::abs(index_dist(mt))); + check_parallelize_loop_no_return(pool, start, end, task_dist(mt), true); + dual_println("Trying the overload of push_loop() for the case where the first index is equal to 0:"); + check_parallelize_loop_no_return(pool, 0, index_dist(mt), task_dist(mt), true); +} + +// =============================================== +// Functions to verify task monitoring and control +// =============================================== + +/** + * @brief Check that task monitoring works. + * + * @param pool The thread pool to check. + */ +void check_task_monitoring(BS::thread_pool& pool) +{ + BS::concurrency_t n = std::min(std::thread::hardware_concurrency(), 4); + dual_println("Resetting pool to ", n, " threads."); + pool.reset(n); + dual_println("Submitting ", n * 3, " tasks."); + std::unique_ptr[]> release = std::make_unique[]>(n * 3); + for (BS::concurrency_t i = 0; i < n * 3; ++i) + pool.push_task( + [&release, i] + { + while (!release[i]) + std::this_thread::yield(); + dual_println("Task ", i, " released."); + }); + constexpr std::chrono::milliseconds sleep_time(300); + std::this_thread::sleep_for(sleep_time); + + dual_println("After submission, should have: ", n * 3, " tasks total, ", n, " tasks running, ", n * 2, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n * 3 && pool.get_tasks_running() == n && pool.get_tasks_queued() == n * 2); + for (BS::concurrency_t i = 0; i < n; ++i) + release[i] = true; + std::this_thread::sleep_for(sleep_time); + + dual_println("After releasing ", n, " tasks, should have: ", n * 2, " tasks total, ", n, " tasks running, ", n, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n * 2 && pool.get_tasks_running() == n && pool.get_tasks_queued() == n); + for (BS::concurrency_t i = n; i < n * 2; ++i) + release[i] = true; + std::this_thread::sleep_for(sleep_time); + + dual_println("After releasing ", n, " more tasks, should have: ", n, " tasks total, ", n, " tasks running, ", 0, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n && pool.get_tasks_running() == n && pool.get_tasks_queued() == 0); + for (BS::concurrency_t i = n * 2; i < n * 3; ++i) + release[i] = true; + std::this_thread::sleep_for(sleep_time); + + dual_println("After releasing the final ", n, " tasks, should have: ", 0, " tasks total, ", 0, " tasks running, ", 0, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == 0 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == 0); + + dual_println("Resetting pool to ", std::thread::hardware_concurrency(), " threads."); + pool.reset(std::thread::hardware_concurrency()); +} + +/** + * @brief Check that pausing works. + * + * @param pool The thread pool to check. + */ +void check_pausing(BS::thread_pool& pool) +{ + BS::concurrency_t n = std::min(std::thread::hardware_concurrency(), 4); + dual_println("Resetting pool to ", n, " threads."); + pool.reset(n); + dual_println("Checking that the pool correctly reports that it is not paused."); + check(pool.is_paused() == false); + dual_println("Pausing pool."); + pool.pause(); + dual_println("Checking that the pool correctly reports that it is paused."); + check(pool.is_paused() == true); + dual_println("Submitting ", n * 3, " tasks, each one waiting for 200ms."); + for (BS::concurrency_t i = 0; i < n * 3; ++i) + pool.push_task( + [i] + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + dual_println("Task ", i, " done."); + }); + + dual_println("Immediately after submission, should have: ", n * 3, " tasks total, ", 0, " tasks running, ", n * 3, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n * 3 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n * 3); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + dual_println("300ms later, should still have: ", n * 3, " tasks total, ", 0, " tasks running, ", n * 3, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n * 3 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n * 3); + dual_println("Unpausing pool."); + pool.unpause(); + dual_println("Checking that the pool correctly reports that it is not paused."); + check(pool.is_paused() == false); + std::this_thread::sleep_for(std::chrono::milliseconds(300)); + + dual_println("300ms later, should have: ", n * 2, " tasks total, ", n, " tasks running, ", n, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n * 2 && pool.get_tasks_running() == n && pool.get_tasks_queued() == n); + dual_println("Pausing pool and using wait_for_tasks() to wait for the running tasks."); + pool.pause(); + pool.wait_for_tasks(); + + dual_println("After waiting, should have: ", n, " tasks total, ", 0, " tasks running, ", n, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + dual_println("200ms later, should still have: ", n, " tasks total, ", 0, " tasks running, ", n, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == n && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == n); + dual_println("Unpausing pool and using wait_for_tasks() to wait for all tasks."); + pool.unpause(); + pool.wait_for_tasks(); + + dual_println("After waiting, should have: ", 0, " tasks total, ", 0, " tasks running, ", 0, " tasks queued..."); + dual_print("Result: ", pool.get_tasks_total(), " tasks total, ", pool.get_tasks_running(), " tasks running, ", pool.get_tasks_queued(), " tasks queued "); + check(pool.get_tasks_total() == 0 && pool.get_tasks_running() == 0 && pool.get_tasks_queued() == 0); + + dual_println("Resetting pool to ", std::thread::hardware_concurrency(), " threads."); + pool.reset(std::thread::hardware_concurrency()); +} + +/** + * @brief Check that purge() works. + * + * @param pool The thread pool to check. + */ +void check_purge(BS::thread_pool& pool) +{ + dual_println("Resetting pool to 1 thread."); + pool.reset(1); + constexpr size_t num_tasks = 10; + dual_println("Submitting ", num_tasks, " tasks to the pool."); + std::unique_ptr[]> done = std::make_unique[]>(num_tasks); + for (size_t i = 0; i < num_tasks; ++i) + pool.push_task( + [&done, i] + { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + dual_println("Task ", i, " done."); + done[i] = true; + }); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + dual_println("Purging the pool and waiting for tasks..."); + pool.purge(); + pool.wait_for_tasks(); + dual_println("Checking that only the first task was executed..."); + check(done[0]); + bool any_flag_set = false; + for (BS::concurrency_t i = 1; i < num_tasks; ++i) + any_flag_set = any_flag_set || done[i]; + check(!any_flag_set); + + dual_println("Resetting pool to ", std::thread::hardware_concurrency(), " threads."); + pool.reset(std::thread::hardware_concurrency()); +} + +// ====================================== +// Functions to verify exception handling +// ====================================== + +/** + * @brief Check that exceptions are forwarded correctly by submit(). + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_exceptions_submit(P& pool) +{ + dual_println("Checking that exceptions are forwarded correctly by submit()..."); + bool caught = false; + auto throws = [] + { + dual_println("Throwing exception..."); + throw std::runtime_error("Exception thrown!"); + }; + std::future my_future = pool.submit(throws); + try + { + my_future.get(); + } + catch (const std::exception& e) + { + if (e.what() == std::string("Exception thrown!")) + caught = true; + } + check(caught); +} + +/** + * @brief Check that exceptions are forwarded correctly by BS::multi_future. + * + * @param pool The thread pool to check. + */ +void check_exceptions_multi_future(BS::thread_pool& pool) +{ + dual_println("Checking that exceptions are forwarded correctly by BS::multi_future..."); + bool caught = false; + auto throws = [] + { + dual_println("Throwing exception..."); + throw std::runtime_error("Exception thrown!"); + }; + BS::multi_future my_future; + my_future.push_back(pool.submit(throws)); + my_future.push_back(pool.submit(throws)); + try + { + void(my_future.get()); + } + catch (const std::exception& e) + { + if (e.what() == std::string("Exception thrown!")) + caught = true; + } + check(caught); +} + +// ===================================== +// Functions to verify vector operations +// ===================================== + +/** + * @brief Check that parallelized vector operations work as expected by calculating the sum of two randomized vectors of a specific size in two ways, single-threaded and multithreaded, and comparing the results. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + * @param vector_size The size of the vectors. + * @param num_tasks The number of tasks to split the calculation into. + */ +template +void check_vector_of_size(P& pool, const size_t vector_size, const BS::concurrency_t num_tasks) +{ + std::vector vector_1(vector_size); + std::vector vector_2(vector_size); + std::mt19937_64 mt(rd()); + std::uniform_int_distribution vector_dist(-1000000, 1000000); + for (size_t i = 0; i < vector_size; ++i) + { + vector_1[i] = vector_dist(mt); + vector_2[i] = vector_dist(mt); + } + dual_println("Adding two vectors with ", vector_size, " elements using ", num_tasks, " tasks..."); + std::vector sum_single(vector_size); + for (size_t i = 0; i < vector_size; ++i) + sum_single[i] = vector_1[i] + vector_2[i]; + std::vector sum_multi(vector_size); + pool.push_loop( + 0, vector_size, + [&sum_multi, &vector_1, &vector_2](const size_t start, const size_t end) + { + for (size_t i = start; i < end; ++i) + sum_multi[i] = vector_1[i] + vector_2[i]; + }, + num_tasks); + pool.wait_for_tasks(); + bool vectors_equal = true; + for (size_t i = 0; i < vector_size; ++i) + vectors_equal = vectors_equal && (sum_single[i] == sum_multi[i]); + check(vectors_equal); +} + +/** + * @brief Check that parallelized vector operations work as expected by calculating the sum of two randomized vectors in two ways, single-threaded and multithreaded, and comparing the results. + * + * @tparam P The type of the thread pool to check. + * @param pool The thread pool to check. + */ +template +void check_vectors(P& pool) +{ + std::mt19937_64 mt(rd()); + std::uniform_int_distribution size_dist(0, 1000000); + std::uniform_int_distribution task_dist(1, pool.get_thread_count()); + for (size_t i = 0; i < 10; ++i) + check_vector_of_size(pool, size_dist(mt), task_dist(mt)); +} + +// ================== +// Main test function +// ================== + +/** + * @brief Test that various aspects of the library are working as expected. + */ +void do_tests() +{ + print_header("Checking that the constructor works in the full thread pool:"); + check_constructor(pool_full); + print_header("Checking that the constructor works in the light thread pool:"); + check_constructor(pool_light); + + print_header("Checking that reset() works in the full thread pool:"); + check_reset(pool_full); + + print_header("Checking that push_task() works in the full thread pool:"); + check_push_task(pool_full); + print_header("Checking that push_task() works in the light thread pool:"); + check_push_task(pool_light); + + print_header("Checking that submit() works in the full thread pool:"); + check_submit(pool_full); + print_header("Checking that submit() works in the light thread pool:"); + check_submit(pool_light); + + print_header("Checking that submitting member functions works in the full thread pool:"); + check_member_function(pool_full); + print_header("Checking that submitting member functions works in the light thread pool:"); + check_member_function(pool_light); + + print_header("Checking that submitting member functions from within an object works in the full thread pool:"); + check_member_function_within_object(pool_full); + print_header("Checking that submitting member functions from within an object works in the light thread pool:"); + check_member_function_within_object(pool_light); + + print_header("Checking that wait_for_tasks() works in the full thread pool..."); + check_wait_for_tasks(pool_full); + check_wait_for_tasks_blocks(pool_full); + check_wait_for_tasks_deadlock(); + check_wait_for_tasks_duration(pool_full); + check_wait_for_tasks_until(pool_full); + print_header("Checking that wait_for_tasks() works in the light thread pool..."); + check_wait_for_tasks(pool_light); + check_wait_for_tasks_blocks(pool_full); + check_wait_for_tasks_deadlock(); + + print_header("Checking that push_loop() and parallelize_loop() work in the full thread pool:"); + check_loop(pool_full); + print_header("Checking that push_loop() works in the light thread pool:"); + check_loop(pool_light); + + print_header("Checking that task monitoring works in the full thread pool:"); + check_task_monitoring(pool_full); + + print_header("Checking that pausing works in the full thread pool:"); + check_pausing(pool_full); + + print_header("Checking that purge() works in the full thread pool:"); + check_purge(pool_full); + + print_header("Checking that exception handling works in the full thread pool:"); + check_exceptions_submit(pool_full); + check_exceptions_multi_future(pool_full); + print_header("Checking that exception handling works in the light thread pool:"); + check_exceptions_submit(pool_light); + + print_header("Testing that vector operations produce the expected results in the full thread pool:"); + check_vectors(pool_full); + print_header("Testing that vector operations produce the expected results in the light thread pool:"); + check_vectors(pool_light); + + if (enable_long_deadlock_tests) + { + print_header("Checking for deadlocks:"); + dual_println("Checking for destruction deadlocks in the full thread pool..."); + check_deadlock([] { BS::thread_pool temp_pool; }); + dual_println("Checking for destruction deadlocks in the light thread pool..."); + check_deadlock([] { BS::thread_pool_light temp_pool; }); + dual_println("Checking for reset deadlocks in the full thread pool..."); + BS::thread_pool temp_pool; + check_deadlock([&temp_pool] { temp_pool.reset(); }); + } +} + +// ========================== +// Functions for benchmarking +// ========================== + +/** + * @brief Print the timing of a specific test. + * + * @param num_tasks The number of tasks. + * @param mean_sd An std::pair containing the mean as the first member and standard deviation as the second member. + */ +void print_timing(const BS::concurrency_t num_tasks, const std::pair& mean_sd) +{ + if (num_tasks == 0) + dual_print("Single-threaded"); + else if (num_tasks == 1) + dual_print("With 1 task"); + else + dual_print("With ", std::setw(4), num_tasks, " tasks"); + dual_println(", mean execution time was ", std::setw(6), mean_sd.first, " ms with standard deviation ", std::setw(4), mean_sd.second, " ms."); +} + +/** + * @brief Calculate and print the speedup obtained by multithreading. + * + * @param timings A vector of the timings corresponding to different numbers of tasks. + */ +void print_speedup(const std::vector& timings, const BS::concurrency_t try_tasks[]) +{ + const std::vector::const_iterator min_el = std::min_element(std::begin(timings), std::end(timings)); + const double max_speedup = std::round(timings[0] / *min_el * 10) / 10; + const BS::concurrency_t num_tasks = try_tasks[min_el - std::begin(timings)]; + dual_println("Maximum speedup obtained by multithreading vs. single-threading: ", max_speedup, "x, using ", num_tasks, " tasks."); +} + +/** + * @brief Calculate the mean and standard deviation of a set of integers. + * + * @param timings The integers. + * @return An std::pair containing the mean as the first member and standard deviation as the second member. + */ +std::pair analyze(const std::vector& timings) +{ + double mean = 0; + for (size_t i = 0; i < timings.size(); ++i) + mean += static_cast(timings[i]) / static_cast(timings.size()); + double variance = 0; + for (size_t i = 0; i < timings.size(); ++i) + variance += (static_cast(timings[i]) - mean) * (static_cast(timings[i]) - mean) / static_cast(timings.size()); + const double sd = std::sqrt(variance); + return {mean, sd}; +} + +/** + * @brief A function to generate vector elements. Chosen arbitrarily to simulate a typical numerical calculation. + * + * @param i The vector number. + * @param j The element index. + * @return The value of the element. + */ +double generate_element(const size_t i, const size_t j) +{ + return std::log(std::sqrt(std::exp(std::sin(i) + std::cos(j)))); +} + +/** + * @brief Benchmark multithreaded performance. + */ +void check_performance() +{ + // Create a new pool to ensure that we have a fresh start. We use the light version here since we don't need any of the advanced features of the full version. + BS::thread_pool_light pool; + + // Set the formatting of floating point numbers. + dual_print(std::fixed, std::setprecision(1)); + + // Initialize a timer object to measure execution time. + BS::timer tmr; + + // Store the number of available hardware threads for easy access. + const BS::concurrency_t thread_count = pool.get_thread_count(); + dual_println("Using ", thread_count, " threads."); + + // Define the number of tasks to try in each run of the test (0 = single-threaded). + const BS::concurrency_t try_tasks[] = {0, thread_count / 4, thread_count / 2, thread_count, thread_count * 2, thread_count * 4}; + + // How many times to repeat each run of the test in order to collect reliable statistics. + constexpr size_t repeat = 20; + dual_println("Each test will be repeated ", repeat, " times to collect reliable statistics."); + + // The target execution time, in milliseconds, of the multi-threaded test with the number of blocks equal to the number of threads. The total time spent on that test will be approximately equal to repeat * target_ms. + constexpr std::chrono::milliseconds::rep target_ms = 50; + + // Test how many vectors we need to generate, and of what size, to roughly achieve the target execution time. + dual_println("Determining the number and size of vectors to generate in order to achieve an approximate mean execution time of ", target_ms, " ms with ", thread_count, " tasks..."); + size_t num_vectors = thread_count; + size_t vector_size = thread_count; + std::vector> vectors; + auto loop = [&vectors, &vector_size](const size_t start, const size_t end) + { + for (size_t i = start; i < end; ++i) + { + for (size_t j = 0; j < vector_size; ++j) + vectors[i][j] = generate_element(i, j); + } + }; + do + { + num_vectors *= 2; + vector_size *= 2; + vectors = std::vector>(num_vectors, std::vector(vector_size)); + tmr.start(); + pool.push_loop(num_vectors, loop); + pool.wait_for_tasks(); + tmr.stop(); + } while (tmr.ms() < target_ms); + num_vectors = thread_count * static_cast(std::llround(static_cast(num_vectors) * static_cast(target_ms) / static_cast(tmr.ms()) / thread_count)); + + // Initialize the desired number of vectors. + vectors = std::vector>(num_vectors, std::vector(vector_size)); + + // Define vectors to store statistics. + std::vector different_n_timings; + std::vector same_n_timings; + + // Perform the test. + dual_println("Generating ", num_vectors, " vectors with ", vector_size, " elements each:"); + for (BS::concurrency_t n : try_tasks) + { + for (size_t r = 0; r < repeat; ++r) + { + tmr.start(); + if (n > 1) + { + pool.push_loop(num_vectors, loop, n); + pool.wait_for_tasks(); + } + else + { + for (size_t i = 0; i < num_vectors; ++i) + { + for (size_t j = 0; j < vector_size; ++j) + vectors[i][j] = generate_element(i, j); + } + } + tmr.stop(); + same_n_timings.push_back(tmr.ms()); + } + std::pair mean_sd = analyze(same_n_timings); + print_timing(n, mean_sd); + different_n_timings.push_back(mean_sd.first); + same_n_timings.clear(); + } + print_speedup(different_n_timings, try_tasks); +} + +int main() +{ + const std::string log_filename = "BS_thread_pool_test-" + get_time() + ".log"; + if (output_log) + log_file.open(log_filename); + + dual_println("BS::thread_pool: a fast, lightweight, and easy-to-use C++17 thread pool library"); + dual_println("(c) 2023 Barak Shoshany (baraksh@gmail.com) (http://baraksh.com)"); + dual_println("GitHub: https://github.com/bshoshany/thread-pool\n"); + + dual_println("Thread pool library version is ", BS_THREAD_POOL_VERSION, "."); + dual_println("Light thread pool library version is ", BS_THREAD_POOL_LIGHT_VERSION, "."); + dual_println("Hardware concurrency is ", std::thread::hardware_concurrency(), "."); + if (output_log) + dual_println("Generating log file: ", log_filename, ".\n"); + + dual_println("Important: Please do not run any other applications, especially multithreaded applications, in parallel with this test!"); + + if (enable_tests) + do_tests(); + + if (tests_failed == 0) + { + if (enable_tests) + print_header("SUCCESS: Passed all " + std::to_string(tests_succeeded) + " checks!", '+'); + if (enable_benchmarks) + { + print_header("Performing benchmarks:"); + check_performance(); + print_header("Thread pool performance test completed!", '+'); + } + return 0; + } + else + { + print_header("FAILURE: Passed " + std::to_string(tests_succeeded) + " checks, but failed " + std::to_string(tests_failed) + "!", '+'); + dual_println("\nPlease submit a bug report at https://github.com/bshoshany/thread-pool/issues including the exact specifications of your system (OS, CPU, compiler, etc.) and the generated log file."); + std::quick_exit(static_cast(tests_failed)); + } +} diff --git a/external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.ps1 b/external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.ps1 new file mode 100644 index 000000000..cf19f1500 --- /dev/null +++ b/external/include/common/thread-pool-3.5.0/tests/BS_thread_pool_test.ps1 @@ -0,0 +1,224 @@ +#!/usr/bin/env pwsh +$Host.UI.RawUI.WindowTitle = 'BS::thread_pool Test Script' + +$TitleColor = 'Green' +$TextColor = 'Yellow' +$ErrorColor = 'Red' +$CommandColor = 'Blue' + +Function Write-Title([String] $Title) +{ + If ($Title.Length -gt 0) + { + $Separator = '=' * $Title.Length + Write-Host + Write-Host $Separator -ForegroundColor $TitleColor + Write-Host $Title -ForegroundColor $TitleColor + Write-Host $Separator -ForegroundColor $TitleColor + Write-Host + } +} + +Function Write-Text([String] $Text) +{ + Write-Host $Text -ForegroundColor $TextColor +} + +Function Write-Error([String] $Text) +{ + Write-Host $Text -ForegroundColor $ErrorColor +} + +Function Write-Command([String] $Text) +{ + Write-Host $Text -ForegroundColor $CommandColor +} + +Function Exit-Script([Int] $Code) +{ + Set-Location $CurrentDir + Exit $Code +} + +$CurrentDir = Get-Location + +Write-Title '=== BS::thread_pool test script ===' + +$SourceFile = 'BS_thread_pool_test.cpp' +If (Test-Path -Path $SourceFile) +{ + Write-Text "Found source file $SourceFile." +} +Else +{ + Write-Error "Source file $SourceFile not found, aborting script!" + Exit-Script 1 +} + +$BuildFolder = Join-Path '..' 'build' +If (Test-Path -Path $BuildFolder) +{ + Write-Text 'Cleaning up build folder...' + Remove-Item (Join-Path $BuildFolder '*') +} +Else +{ + Write-Text 'Creating build folder...' + $Null = New-Item $BuildFolder -ItemType 'Directory' +} +Write-Text 'Done.' + +Write-Title 'Compiling...' + +If ($IsWindows) +{ + $Extension = '.exe' + $PThread = '' +} +Else +{ + $Extension = '' + $PThread = ' -pthread' +} + +$ClangExe = 'BS_thread_pool_test_clang' + $Extension +$GCCExe = 'BS_thread_pool_test_gcc' + $Extension + +$MSVCName = 'BS_thread_pool_test_msvc' +$MSVCExe = $MSVCName + $Extension +$MSVCObj = $MSVCName + '.obj' + +If (Get-Command 'clang++' -ErrorAction SilentlyContinue) +{ + $Clang = $True + $FullClangExe = Join-Path $BuildFolder $ClangExe + Write-Text 'Compiling with Clang...' + $ClangCommand = "clang++ $SourceFile$PThread -std=c++17 -O3 -Wall -Wextra -Wconversion -Wsign-conversion -Wpedantic -Weffc++ -Wshadow -o $FullClangExe" + Write-Command $ClangCommand + Invoke-Expression $ClangCommand + If ($LASTEXITCODE) + { + Write-Error "Failed to compile, aborting script! (Exit code: $LASTEXITCODE)" + Exit-Script $LASTEXITCODE + } + Else + { + Write-Text "Successfully compiled to $FullClangExe." + } +} +Else +{ + $Clang = $False + Write-Error 'Clang not found, skipping it!' +} + +Write-Host + +If (Get-Command 'g++' -ErrorAction SilentlyContinue) +{ + $GCC = $True + $FullGCCExe = Join-Path $BuildFolder $GCCExe + Write-Text 'Compiling with GCC...' + $GCCCommand = "g++ $SourceFile$PThread -std=c++17 -O3 -Wall -Wextra -Wconversion -Wsign-conversion -Wpedantic -Weffc++ -Wshadow -o $FullGCCExe" + Write-Command $GCCCommand + Invoke-Expression $GCCCommand + If ($LASTEXITCODE) + { + Write-Error "Failed to compile, aborting script! (Exit code: $LASTEXITCODE)" + Exit-Script $LASTEXITCODE + } + Else + { + Write-Text "Successfully compiled to $FullGCCExe." + } +} +Else +{ + $GCC = $False + Write-Error 'GCC not found, skipping it!' +} + +Write-Host + +If ($IsWindows) +{ + $MSVCEnv = 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1' + If (Test-Path $MSVCEnv) + { + $MSVC = $True + $FullMSVCExe = Join-Path $BuildFolder $MSVCExe + $FullMSVCObj = Join-Path $BuildFolder $MSVCObj + Write-Text 'Compiling with MSVC...' + $Env:BS_THREAD_POOL_MSVC_COMMAND = "cl $SourceFile /std:c++17 /permissive- /O2 /W4 /EHsc /Fe:$FullMSVCExe /Fo:$FullMSVCObj" + Write-Command $Env:BS_THREAD_POOL_MSVC_COMMAND + pwsh -Command { + $CurrentDir = Get-Location + & 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\Tools\Launch-VsDevShell.ps1' + Set-Location $CurrentDir + Invoke-Expression $Env:BS_THREAD_POOL_MSVC_COMMAND + Exit $LASTEXITCODE + } + $Env:BS_THREAD_POOL_MSVC_COMMAND = $Null + If ($LASTEXITCODE) + { + Write-Error "Failed to compile, aborting script! (Exit code: $LASTEXITCODE)" + Exit-Script $LASTEXITCODE + } + Else + { + Remove-Item $FullMSVCObj + Write-Text "Successfully compiled to $FullMSVCExe." + } + } + Else + { + $MSVC = $False + Write-Error 'MSVC not found, skipping it!' + } +} +Else +{ + $MSVC = $False +} + +Write-Title 'Starting tests...' + +Set-Location $BuildFolder +$Repeats = 5; + +Write-Text "Each test will be repeated $Repeats times. Outputs can be found in the generated log files." + +Function Invoke-Test([String] $Compiler, [String] $Executable) +{ + Write-Title "Starting $Compiler tests..." + For ($i = 1; $i -Le $Repeats; $i++) + { + Write-Text "Running $Compiler test #$i of $Repeats..." + Invoke-Expression $Executable | Out-Null + if ($LASTEXITCODE) + { + Write-Error "Test Failed, aborting script! (Exit code: $LASTEXITCODE)" + Exit-Script $LASTEXITCODE + } + Write-Text 'Test finished successfully!' + Write-Host + } + Write-Text "$Compiler tests done." +} + +if ($Clang) +{ + Invoke-Test 'Clang' (Join-Path '.' $ClangExe) +} +if ($GCC) +{ + Invoke-Test 'GCC' (Join-Path '.' $GCCExe) +} +if ($MSVC) +{ + Invoke-Test 'MSVC' (Join-Path '.' $MSVCExe) +} + +Write-Title '=== All tests completed successfully! ===' + +Exit-Script 0 From 34ac535e69e034bf9efc35d487f6d90e363fe1c3 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sat, 18 Nov 2023 23:55:58 +0000 Subject: [PATCH 02/17] Threadpooling for Lua GC --- Main.cpp | 3 +++ Managers/LuaMan.cpp | 16 +++++++++------- Managers/LuaMan.h | 2 +- Managers/ThreadMan.h | 18 +++++------------- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Main.cpp b/Main.cpp index 93038d237..7a0709752 100644 --- a/Main.cpp +++ b/Main.cpp @@ -46,6 +46,7 @@ #include "CameraMan.h" #include "ActivityMan.h" #include "PrimitiveMan.h" +#include "ThreadMan.h" #include "tracy/Tracy.hpp" @@ -61,6 +62,7 @@ namespace RTE { /// Initializes all the essential managers. /// void InitializeManagers() { + ThreadMan::Construct(); TimerMan::Construct(); PresetMan::Construct(); SettingsMan::Construct(); @@ -84,6 +86,7 @@ namespace RTE { ActivityMan::Construct(); LoadingScreen::Construct(); + g_ThreadMan.Initialize(); g_SettingsMan.Initialize(); g_WindowMan.Initialize(); diff --git a/Managers/LuaMan.cpp b/Managers/LuaMan.cpp index cf2fbbf25..1b1d9cd2b 100644 --- a/Managers/LuaMan.cpp +++ b/Managers/LuaMan.cpp @@ -1,6 +1,9 @@ #include "LuaMan.h" + #include "LuabindObjectWrapper.h" #include "LuaBindingRegisterDefinitions.h" +#include "ThreadMan.h" + #include "tracy/Tracy.hpp" #include "tracy/TracyLua.hpp" @@ -389,8 +392,8 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void LuaMan::ClearUserModuleCache() { - if (m_GCThread.joinable()) { - m_GCThread.join(); + if (m_GarbageCollectionTask.valid()) { + m_GarbageCollectionTask.wait(); } m_ScriptMultithreadedtyMap.clear(); @@ -1098,10 +1101,9 @@ namespace RTE { } // Make sure a GC run isn't happening while we try to apply deletions - if (m_GCThread.joinable()) { - m_GCThread.join(); + if (m_GarbageCollectionTask.valid()) { + m_GarbageCollectionTask.wait(); } - // Apply all deletions queued from lua LuabindObjectWrapper::ApplyQueuedDeletions(); } @@ -1110,9 +1112,9 @@ namespace RTE { void LuaMan::StartAsyncGarbageCollection() { ZoneScoped; - + // Start a new thread to perform the GC run. - m_GCThread = std::thread([this]() { + m_GarbageCollectionTask = g_ThreadMan.GetThreadPool().submit([this]() { std::vector allStates; allStates.reserve(m_ScriptStates.size() + 1); diff --git a/Managers/LuaMan.h b/Managers/LuaMan.h index 5958ccf36..bb7be1a02 100644 --- a/Managers/LuaMan.h +++ b/Managers/LuaMan.h @@ -515,7 +515,7 @@ namespace RTE { int m_LastAssignedLuaState = 0; - std::thread m_GCThread; + std::future m_GarbageCollectionTask; ///

/// Clears all the member variables of this LuaMan, effectively resetting the members of this abstraction level only. diff --git a/Managers/ThreadMan.h b/Managers/ThreadMan.h index 0840e8fab..e2046acc7 100644 --- a/Managers/ThreadMan.h +++ b/Managers/ThreadMan.h @@ -50,6 +50,11 @@ class ThreadMan: ThreadMan() { Clear(); Create(); } + /// + /// Makes the TimerMan object ready for use. + /// + void Initialize() { }; + ////////////////////////////////////////////////////////////////////////////////////////// // Destructor: ~ThreadMan @@ -95,24 +100,11 @@ class ThreadMan: BS::thread_pool& GetThreadPool() { return m_ThreadPool; } - -////////////////////////////////////////////////////////////////////////////////////////// -// Virtual method: GetClassName -////////////////////////////////////////////////////////////////////////////////////////// -// Description: Gets the class name of this Entity. -// Arguments: None. -// Return value: A string with the friendly-formatted type name of this object. - - virtual const std::string & GetClassName() const { return m_ClassName; } - ////////////////////////////////////////////////////////////////////////////////////////// // Protected member variable and method declarations protected: - // Member variables - static const std::string m_ClassName; - ////////////////////////////////////////////////////////////////////////////////////////// From 6aa4f99a87f5876d67f5925a9fca43104da072c9 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 19 Nov 2023 00:05:23 +0000 Subject: [PATCH 03/17] Thread-pooled pathfinding and scene BG clearing. Big improvements to stop framespikes --- Entities/SceneLayer.cpp | 9 ++++----- Entities/SceneLayer.h | 2 +- System/PathFinder.cpp | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Entities/SceneLayer.cpp b/Entities/SceneLayer.cpp index e981a1dab..0e5d231cd 100644 --- a/Entities/SceneLayer.cpp +++ b/Entities/SceneLayer.cpp @@ -4,6 +4,7 @@ #include "SceneMan.h" #include "SettingsMan.h" #include "ActivityMan.h" +#include "ThreadMan.h" namespace RTE { @@ -293,8 +294,8 @@ namespace RTE { void SceneLayerImpl::ClearBitmap(ColorKeys clearTo) { RTEAssert(m_MainBitmapOwned, "Bitmap not owned! We shouldn't be clearing this!"); - if (m_BitmapClearThread.joinable()) { - m_BitmapClearThread.join(); + if (m_BitmapClearTask.valid()) { + m_BitmapClearTask.wait(); } if (m_LastClearColor != clearTo) { @@ -306,12 +307,10 @@ namespace RTE { std::swap(m_MainBitmap, m_BackBitmap); // Start a new thread to clear the backbuffer bitmap asynchronously. - m_BitmapClearThread = std::thread([this, clearTo](BITMAP *bitmap, std::vector drawings) { + m_BitmapClearTask = g_ThreadMan.GetThreadPool().submit([this, clearTo](BITMAP *bitmap, std::vector drawings) { ClearDrawings(bitmap, drawings, clearTo); }, m_BackBitmap, m_Drawings); - m_BitmapClearThread.detach(); - m_Drawings.clear(); // This was copied into the new thread, so can be safely deleted. } diff --git a/Entities/SceneLayer.h b/Entities/SceneLayer.h index fba9b39d2..9b91cabe9 100644 --- a/Entities/SceneLayer.h +++ b/Entities/SceneLayer.h @@ -282,7 +282,7 @@ namespace RTE { BITMAP *m_BackBitmap; //!< The backbuffer BITMAP of this SceneLayer. // We use two bitmaps, as a backbuffer. While the main bitmap is being used, the secondary bitmap will be cleared on a separate thread. This is because we tend to want to clear some scene layers every frame and that is costly. - std::thread m_BitmapClearThread; //!< Thread for clearing BITMAP in background. + std::future m_BitmapClearTask; //!< Task for clearing BITMAP async in background. ColorKeys m_LastClearColor; //!< The last color we cleared this SceneLayer to. std::vector m_Drawings; //!< All the areas drawn within on this SceneLayer since the last clear. diff --git a/System/PathFinder.cpp b/System/PathFinder.cpp index d75c0543b..f994d848c 100644 --- a/System/PathFinder.cpp +++ b/System/PathFinder.cpp @@ -3,6 +3,7 @@ #include "Material.h" #include "Scene.h" #include "SceneMan.h" +#include "ThreadMan.h" namespace RTE { @@ -207,7 +208,7 @@ namespace RTE { const_cast(pathRequest->startPos) = start; const_cast(pathRequest->targetPos) = end; - std::thread pathThread([this, start, end, digStrength, callback](std::shared_ptr volRequest) { + g_ThreadMan.GetThreadPool().push_task([this, start, end, digStrength, callback](std::shared_ptr volRequest) { // Cast away the volatile-ness - only matters outside (and complicates the API otherwise) PathRequest &request = const_cast(*volRequest); @@ -225,7 +226,6 @@ namespace RTE { request.complete = true; }, pathRequest); - pathThread.detach(); return pathRequest; } From c4b7962b11a6283eb35baaf03c9076f68e891b61 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 19 Nov 2023 00:33:23 +0000 Subject: [PATCH 04/17] Use as many lua states as our hardware allows concurrency --- Managers/LuaMan.cpp | 4 +++- Managers/LuaMan.h | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Managers/LuaMan.cpp b/Managers/LuaMan.cpp index 1b1d9cd2b..de7590b46 100644 --- a/Managers/LuaMan.cpp +++ b/Managers/LuaMan.cpp @@ -289,6 +289,8 @@ namespace RTE { void LuaMan::Initialize() { m_MasterScriptState.Initialize(); + + m_ScriptStates = std::vector(g_ThreadMan.GetThreadPool().get_thread_count()); for (LuaStateWrapper &luaState : m_ScriptStates) { luaState.Initialize(); } @@ -348,7 +350,7 @@ namespace RTE { return &(*itr);*/ int ourState = m_LastAssignedLuaState; - m_LastAssignedLuaState = (m_LastAssignedLuaState + 1) % c_NumThreadedLuaStates; + m_LastAssignedLuaState = (m_LastAssignedLuaState + 1) % m_ScriptStates.size(); bool success = m_ScriptStates[ourState].GetMutex().try_lock(); RTEAssert(success, "Script mutex was already locked while in a non-multithreaded environment!"); diff --git a/Managers/LuaMan.h b/Managers/LuaMan.h index bb7be1a02..8f3adfc5f 100644 --- a/Managers/LuaMan.h +++ b/Managers/LuaMan.h @@ -311,8 +311,7 @@ namespace RTE { RandomGenerator m_RandomGenerator; //!< The random number generator used for this lua state. }; - static constexpr int c_NumThreadedLuaStates = 16; - typedef std::array LuaStatesArray; + typedef std::vector LuaStatesArray; /// /// The singleton manager of each Lua state. From f0c370d5111145a042830d63d7acecccedbd9f0c Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 19 Nov 2023 22:19:34 +0000 Subject: [PATCH 05/17] Cut down time spent in on PreControllerUpdate() significantly by filtering against our team --- Entities/AHuman.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Entities/AHuman.cpp b/Entities/AHuman.cpp index e581e0fac..68d4e97ff 100644 --- a/Entities/AHuman.cpp +++ b/Entities/AHuman.cpp @@ -2142,7 +2142,7 @@ void AHuman::PreControllerUpdate() reach += m_pFGArm ? m_pFGArm->GetMaxLength() : m_pBGArm->GetMaxLength(); reachPoint = m_pFGArm ? m_pFGArm->GetJointPos() : m_pBGArm->GetJointPos(); - MOID itemMOID = g_SceneMan.CastMORay(reachPoint, Vector(reach * RandomNum(0.5F, 1.0F) * GetFlipFactor(), 0).RadRotate(m_pItemInReach ? adjustedAimAngle : RandomNum(-(c_HalfPI + c_EighthPI), m_AimAngle * 0.75F + c_EighthPI) * GetFlipFactor()), m_MOID, Activity::NoTeam, g_MaterialGrass, true, 3); + MOID itemMOID = g_SceneMan.CastMORay(reachPoint, Vector(reach * RandomNum(0.5F, 1.0F) * GetFlipFactor(), 0).RadRotate(m_pItemInReach ? adjustedAimAngle : RandomNum(-(c_HalfPI + c_EighthPI), m_AimAngle * 0.75F + c_EighthPI) * GetFlipFactor()), m_MOID, m_Team, g_MaterialGrass, true, 3); if (MovableObject *foundMO = g_MovableMan.GetMOFromID(itemMOID)) { if (HeldDevice *foundDevice = dynamic_cast(foundMO->GetRootParent())) { From 56ba95d16a326bce9576e7ba376eee882f16cc35 Mon Sep 17 00:00:00 2001 From: Causeless Date: Mon, 20 Nov 2023 13:25:43 +0000 Subject: [PATCH 06/17] Fixed framespiking from pathing requests saturating the threadpool --- Entities/SceneLayer.cpp | 2 +- Managers/LuaMan.cpp | 4 ++-- Managers/ThreadMan.cpp | 3 ++- Managers/ThreadMan.h | 10 ++++++++-- System/PathFinder.cpp | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Entities/SceneLayer.cpp b/Entities/SceneLayer.cpp index 0e5d231cd..390cddaa7 100644 --- a/Entities/SceneLayer.cpp +++ b/Entities/SceneLayer.cpp @@ -307,7 +307,7 @@ namespace RTE { std::swap(m_MainBitmap, m_BackBitmap); // Start a new thread to clear the backbuffer bitmap asynchronously. - m_BitmapClearTask = g_ThreadMan.GetThreadPool().submit([this, clearTo](BITMAP *bitmap, std::vector drawings) { + m_BitmapClearTask = g_ThreadMan.GetPriorityThreadPool().submit([this, clearTo](BITMAP *bitmap, std::vector drawings) { ClearDrawings(bitmap, drawings, clearTo); }, m_BackBitmap, m_Drawings); diff --git a/Managers/LuaMan.cpp b/Managers/LuaMan.cpp index de7590b46..9ffa4b08a 100644 --- a/Managers/LuaMan.cpp +++ b/Managers/LuaMan.cpp @@ -290,7 +290,7 @@ namespace RTE { void LuaMan::Initialize() { m_MasterScriptState.Initialize(); - m_ScriptStates = std::vector(g_ThreadMan.GetThreadPool().get_thread_count()); + m_ScriptStates = std::vector(std::thread::hardware_concurrency()); for (LuaStateWrapper &luaState : m_ScriptStates) { luaState.Initialize(); } @@ -1116,7 +1116,7 @@ namespace RTE { ZoneScoped; // Start a new thread to perform the GC run. - m_GarbageCollectionTask = g_ThreadMan.GetThreadPool().submit([this]() { + m_GarbageCollectionTask = g_ThreadMan.GetPriorityThreadPool().submit([this]() { std::vector allStates; allStates.reserve(m_ScriptStates.size() + 1); diff --git a/Managers/ThreadMan.cpp b/Managers/ThreadMan.cpp index 579e8de5f..16f228c46 100644 --- a/Managers/ThreadMan.cpp +++ b/Managers/ThreadMan.cpp @@ -27,7 +27,8 @@ namespace RTE void ThreadMan::Clear() { - m_ThreadPool.reset(); + m_PriorityThreadPool.reset(); + m_BackgroundThreadPool.reset(); } diff --git a/Managers/ThreadMan.h b/Managers/ThreadMan.h index e2046acc7..7cf623a66 100644 --- a/Managers/ThreadMan.h +++ b/Managers/ThreadMan.h @@ -98,7 +98,9 @@ class ThreadMan: void Destroy(); - BS::thread_pool& GetThreadPool() { return m_ThreadPool; } + BS::thread_pool& GetPriorityThreadPool() { return m_PriorityThreadPool; } + + BS::thread_pool& GetBackgroundThreadPool() { return m_BackgroundThreadPool; } ////////////////////////////////////////////////////////////////////////////////////////// // Protected member variable and method declarations @@ -126,7 +128,11 @@ class ThreadMan: ThreadMan(const ThreadMan &reference); ThreadMan & operator=(const ThreadMan &rhs); - BS::thread_pool m_ThreadPool; + // For tasks that we want to be performed ASAP, i.e needs to be complete this frame at some point + BS::thread_pool m_PriorityThreadPool; + + // For background tasks that we can just let happen whenever over multiple frames + BS::thread_pool m_BackgroundThreadPool; }; } // namespace RTE diff --git a/System/PathFinder.cpp b/System/PathFinder.cpp index f994d848c..f21ed39ae 100644 --- a/System/PathFinder.cpp +++ b/System/PathFinder.cpp @@ -208,7 +208,7 @@ namespace RTE { const_cast(pathRequest->startPos) = start; const_cast(pathRequest->targetPos) = end; - g_ThreadMan.GetThreadPool().push_task([this, start, end, digStrength, callback](std::shared_ptr volRequest) { + g_ThreadMan.GetBackgroundThreadPool().push_task([this, start, end, digStrength, callback](std::shared_ptr volRequest) { // Cast away the volatile-ness - only matters outside (and complicates the API otherwise) PathRequest &request = const_cast(*volRequest); From 7b653149ce6e0270eed5c354fea64d0b78a98c5f Mon Sep 17 00:00:00 2001 From: Causeless Date: Mon, 20 Nov 2023 14:26:35 +0000 Subject: [PATCH 07/17] Use threadpools for GC and lua tasks instead of parallel for --- Managers/LuaMan.cpp | 37 +++++++++++++++++-------------------- Managers/LuaMan.h | 4 +++- Managers/MovableMan.cpp | 17 +++++++++++------ Managers/ThreadMan.cpp | 2 +- 4 files changed, 32 insertions(+), 28 deletions(-) diff --git a/Managers/LuaMan.cpp b/Managers/LuaMan.cpp index 9ffa4b08a..8ab3a44af 100644 --- a/Managers/LuaMan.cpp +++ b/Managers/LuaMan.cpp @@ -394,9 +394,7 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void LuaMan::ClearUserModuleCache() { - if (m_GarbageCollectionTask.valid()) { - m_GarbageCollectionTask.wait(); - } + m_GarbageCollectionTask.wait(); m_ScriptMultithreadedtyMap.clear(); @@ -1103,9 +1101,8 @@ namespace RTE { } // Make sure a GC run isn't happening while we try to apply deletions - if (m_GarbageCollectionTask.valid()) { - m_GarbageCollectionTask.wait(); - } + m_GarbageCollectionTask.wait(); + // Apply all deletions queued from lua LuabindObjectWrapper::ApplyQueuedDeletions(); } @@ -1114,26 +1111,26 @@ namespace RTE { void LuaMan::StartAsyncGarbageCollection() { ZoneScoped; + + std::vector allStates; + allStates.reserve(m_ScriptStates.size() + 1); - // Start a new thread to perform the GC run. - m_GarbageCollectionTask = g_ThreadMan.GetPriorityThreadPool().submit([this]() { - std::vector allStates; - allStates.reserve(m_ScriptStates.size() + 1); - - allStates.push_back(&m_MasterScriptState); - for (LuaStateWrapper& wrapper : m_ScriptStates) { - allStates.push_back(&wrapper); - } - - std::for_each(std::execution::par, allStates.begin(), allStates.end(), - [&](LuaStateWrapper* luaState) { + allStates.push_back(&m_MasterScriptState); + for (LuaStateWrapper& wrapper : m_ScriptStates) { + allStates.push_back(&wrapper); + } + + m_GarbageCollectionTask = BS::multi_future(); + for (LuaStateWrapper* luaState : allStates) { + m_GarbageCollectionTask.push_back( + g_ThreadMan.GetPriorityThreadPool().submit([luaState]() { ZoneScopedN("Lua Garbage Collection"); std::lock_guard lock(luaState->GetMutex()); lua_gc(luaState->GetLuaState(), LUA_GCSTEP, 100); lua_gc(luaState->GetLuaState(), LUA_GCSTOP, 0); - } + }) ); - }); + } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Managers/LuaMan.h b/Managers/LuaMan.h index 8f3adfc5f..08ac27a3d 100644 --- a/Managers/LuaMan.h +++ b/Managers/LuaMan.h @@ -6,6 +6,8 @@ #include "RTETools.h" #include "PerformanceMan.h" +#include "BS_thread_pool.hpp" + #define g_LuaMan LuaMan::Instance() struct lua_State; @@ -514,7 +516,7 @@ namespace RTE { int m_LastAssignedLuaState = 0; - std::future m_GarbageCollectionTask; + BS::multi_future m_GarbageCollectionTask; /// /// Clears all the member variables of this LuaMan, effectively resetting the members of this abstraction level only. diff --git a/Managers/MovableMan.cpp b/Managers/MovableMan.cpp index 8bb5c2ae4..8ccf67009 100644 --- a/Managers/MovableMan.cpp +++ b/Managers/MovableMan.cpp @@ -33,6 +33,7 @@ #include "SceneMan.h" #include "SettingsMan.h" #include "LuaMan.h" +#include "ThreadMan.h" #include "tracy/Tracy.hpp" @@ -1738,8 +1739,10 @@ void MovableMan::Update() const std::string threadedUpdate = "ThreadedUpdate"; // avoid string reconstruction LuaStatesArray& luaStates = g_LuaMan.GetThreadedScriptStates(); - std::for_each(std::execution::par, luaStates.begin(), luaStates.end(), - [&](LuaStateWrapper& luaState) { + g_ThreadMan.GetPriorityThreadPool().parallelize_loop(luaStates.size(), + [&](int start, int end) { + RTEAssert(start + 1 == end, "Threaded script state being updated across multiple threads!"); + LuaStateWrapper& luaState = luaStates[start]; g_LuaMan.SetThreadLuaStateOverride(&luaState); for (MovableObject *mo : luaState.GetRegisteredMOs()) { @@ -1747,7 +1750,7 @@ void MovableMan::Update() } g_LuaMan.SetThreadLuaStateOverride(nullptr); - }); + }).wait(); } { @@ -2163,8 +2166,10 @@ void MovableMan::UpdateControllers() } LuaStatesArray& luaStates = g_LuaMan.GetThreadedScriptStates(); - std::for_each(std::execution::par, luaStates.begin(), luaStates.end(), - [&](LuaStateWrapper &luaState) { + g_ThreadMan.GetPriorityThreadPool().parallelize_loop(luaStates.size(), + [&](int start, int end) { + RTEAssert(start + 1 == end, "Threaded script state being updated across multiple threads!"); + LuaStateWrapper& luaState = luaStates[start]; g_LuaMan.SetThreadLuaStateOverride(&luaState); for (Actor *actor : m_Actors) { if (actor->GetLuaState() == &luaState) { @@ -2172,7 +2177,7 @@ void MovableMan::UpdateControllers() } } g_LuaMan.SetThreadLuaStateOverride(nullptr); - }); + }).wait(); for (Actor* actor : m_Actors) { actor->GetController()->UpdateAI(ThreadScriptsToRun::SingleThreaded); diff --git a/Managers/ThreadMan.cpp b/Managers/ThreadMan.cpp index 16f228c46..5b20b6988 100644 --- a/Managers/ThreadMan.cpp +++ b/Managers/ThreadMan.cpp @@ -28,7 +28,7 @@ namespace RTE void ThreadMan::Clear() { m_PriorityThreadPool.reset(); - m_BackgroundThreadPool.reset(); + m_BackgroundThreadPool.reset(std::thread::hardware_concurrency() / 2); } From 6469139709dc069cc52d92213dddc5c136ff95fe Mon Sep 17 00:00:00 2001 From: Causeless Date: Mon, 20 Nov 2023 14:41:36 +0000 Subject: [PATCH 08/17] Added a bunch of extra Tracy zones --- Entities/Scene.cpp | 6 ++++++ Managers/FrameMan.cpp | 4 ++++ Managers/PrimitiveMan.cpp | 5 +++++ Managers/SceneMan.cpp | 10 ++++++++-- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Entities/Scene.cpp b/Entities/Scene.cpp index 7d9cedea0..45968c223 100644 --- a/Entities/Scene.cpp +++ b/Entities/Scene.cpp @@ -44,6 +44,8 @@ #include "Magazine.h" #include "ThrownDevice.h" +#include "tracy/Tracy.hpp" + namespace RTE { ConcreteClassInfo(Scene, Entity, 0); @@ -2967,6 +2969,8 @@ void Scene::BlockUntilAllPathingRequestsComplete() { void Scene::UpdatePathFinding() { + ZoneScoped; + constexpr int nodeUpdatesPerCall = 100; constexpr int maxUnupdatedMaterialAreas = 1000; @@ -3099,6 +3103,8 @@ void Scene::Unlock() void Scene::Update() { + ZoneScoped; + m_PathfindingUpdated = false; if (g_SettingsMan.BlipOnRevealUnseen()) diff --git a/Managers/FrameMan.cpp b/Managers/FrameMan.cpp index cffbf6a8c..bdeb5e43e 100644 --- a/Managers/FrameMan.cpp +++ b/Managers/FrameMan.cpp @@ -22,6 +22,8 @@ #include "GLCheck.h" #include "glad/gl.h" +#include "tracy/Tracy.hpp" + namespace RTE { void BitmapDeleter::operator()(BITMAP *bitmap) const { destroy_bitmap(bitmap); } @@ -801,6 +803,8 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void FrameMan::Draw() { + ZoneScopedN("Draw"); + // Count how many split screens we'll need int screenCount = (m_HSplit ? 2 : 1) * (m_VSplit ? 2 : 1); RTEAssert(screenCount <= 1 || m_PlayerScreen, "Splitscreen surface not ready when needed!"); diff --git a/Managers/PrimitiveMan.cpp b/Managers/PrimitiveMan.cpp index 7244b425d..6fe5b3f69 100644 --- a/Managers/PrimitiveMan.cpp +++ b/Managers/PrimitiveMan.cpp @@ -5,6 +5,8 @@ #include "ConsoleMan.h" #include "MOSprite.h" +#include "tracy/Tracy.hpp" + namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -306,9 +308,12 @@ namespace RTE { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void PrimitiveMan::DrawPrimitives(int player, BITMAP *targetBitmap, const Vector &targetPos) const { + ZoneScoped; + if (m_ScheduledPrimitives.empty()) { return; } + int lastDrawMode = DRAW_MODE_SOLID; DrawBlendMode lastBlendMode = DrawBlendMode::NoBlend; std::array lastBlendAmounts = { BlendAmountLimits::MinBlend, BlendAmountLimits::MinBlend, BlendAmountLimits::MinBlend, BlendAmountLimits::MinBlend }; diff --git a/Managers/SceneMan.cpp b/Managers/SceneMan.cpp index f353c76e9..3f80b9993 100644 --- a/Managers/SceneMan.cpp +++ b/Managers/SceneMan.cpp @@ -36,6 +36,8 @@ // Temp #include "Controller.h" +#include "tracy/Tracy.hpp" + namespace RTE { @@ -2759,7 +2761,9 @@ bool SceneMan::AddSceneObject(SceneObject *sceneObject) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SceneMan::Update(int screenId) { - if (!m_pCurrentScene) { + ZoneScoped; + + if (!m_pCurrentScene) { return; } @@ -2805,7 +2809,9 @@ void SceneMan::Update(int screenId) { ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void SceneMan::Draw(BITMAP *targetBitmap, BITMAP *targetGUIBitmap, const Vector &targetPos, bool skipBackgroundLayers, bool skipTerrain) { - if (!m_pCurrentScene) { + ZoneScoped; + + if (!m_pCurrentScene) { return; } SLTerrain *terrain = m_pCurrentScene->GetTerrain(); From 4c982d4b2e7450671eb7cd6517cf3caa10e0a12a Mon Sep 17 00:00:00 2001 From: Causeless Date: Mon, 20 Nov 2023 14:42:27 +0000 Subject: [PATCH 09/17] Significant optimization to UpdateDrawMOIDs; TODO, replace std::unordered_set with something else entirely --- System/SpatialPartitionGrid.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/System/SpatialPartitionGrid.cpp b/System/SpatialPartitionGrid.cpp index 1f82d6ad5..51abbfb40 100644 --- a/System/SpatialPartitionGrid.cpp +++ b/System/SpatialPartitionGrid.cpp @@ -87,7 +87,6 @@ namespace RTE { for (int x = topLeftCellX; x <= bottomRightCellX; x++) { for (int y = topLeftCellY; y <= bottomRightCellY; y++) { int cellId = GetCellIdForCellCoords(x, y); - m_UsedCellIds.insert(cellId); m_Cells[team + 1][cellId].push_back(mo.GetID()); if (mo.GetsHitByMOs()) { @@ -96,6 +95,12 @@ namespace RTE { } } } + + for (int x = topLeftCellX; x <= bottomRightCellX; x++) { + for (int y = topLeftCellY; y <= bottomRightCellY; y++) { + m_UsedCellIds.insert(GetCellIdForCellCoords(x, y)); + } + } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 76c4da492440d72664870f1b02fea460aeec4701 Mon Sep 17 00:00:00 2001 From: Causeless Date: Mon, 20 Nov 2023 15:07:57 +0000 Subject: [PATCH 10/17] Include and use hopscotch sets to improve UpdateDrawMOIDs performance --- RTEA.vcxproj | 20 +- System/SpatialPartitionGrid.h | 4 +- .../common/hopscotch-map-2.3.1/.codecov.yml | 5 + .../.github/workflows/ci.yml | 136 + .../common/hopscotch-map-2.3.1/CMakeLists.txt | 80 + .../common/hopscotch-map-2.3.1/LICENSE | 21 + .../common/hopscotch-map-2.3.1/README.md | 342 +++ .../cmake/tsl-hopscotch-mapConfig.cmake.in | 9 + .../common/hopscotch-map-2.3.1/doxygen.conf | 2486 +++++++++++++++++ .../include/tsl/bhopscotch_map.h | 737 +++++ .../include/tsl/bhopscotch_set.h | 589 ++++ .../include/tsl/hopscotch_growth_policy.h | 405 +++ .../include/tsl/hopscotch_hash.h | 1896 +++++++++++++ .../include/tsl/hopscotch_map.h | 738 +++++ .../include/tsl/hopscotch_set.h | 595 ++++ .../hopscotch-map-2.3.1/tests/CMakeLists.txt | 26 + .../tests/custom_allocator_tests.cpp | 137 + .../tests/hopscotch_map_tests.cpp | 1357 +++++++++ .../tests/hopscotch_set_tests.cpp | 117 + .../common/hopscotch-map-2.3.1/tests/main.cpp | 26 + .../tests/policy_tests.cpp | 97 + .../common/hopscotch-map-2.3.1/tests/utils.h | 316 +++ .../tsl-hopscotch-map.natvis | 87 + external/include/meson.build | 4 +- 24 files changed, 10218 insertions(+), 12 deletions(-) create mode 100644 external/include/common/hopscotch-map-2.3.1/.codecov.yml create mode 100644 external/include/common/hopscotch-map-2.3.1/.github/workflows/ci.yml create mode 100644 external/include/common/hopscotch-map-2.3.1/CMakeLists.txt create mode 100644 external/include/common/hopscotch-map-2.3.1/LICENSE create mode 100644 external/include/common/hopscotch-map-2.3.1/README.md create mode 100644 external/include/common/hopscotch-map-2.3.1/cmake/tsl-hopscotch-mapConfig.cmake.in create mode 100644 external/include/common/hopscotch-map-2.3.1/doxygen.conf create mode 100644 external/include/common/hopscotch-map-2.3.1/include/tsl/bhopscotch_map.h create mode 100644 external/include/common/hopscotch-map-2.3.1/include/tsl/bhopscotch_set.h create mode 100644 external/include/common/hopscotch-map-2.3.1/include/tsl/hopscotch_growth_policy.h create mode 100644 external/include/common/hopscotch-map-2.3.1/include/tsl/hopscotch_hash.h create mode 100644 external/include/common/hopscotch-map-2.3.1/include/tsl/hopscotch_map.h create mode 100644 external/include/common/hopscotch-map-2.3.1/include/tsl/hopscotch_set.h create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/CMakeLists.txt create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/custom_allocator_tests.cpp create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/hopscotch_map_tests.cpp create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/hopscotch_set_tests.cpp create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/main.cpp create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/policy_tests.cpp create mode 100644 external/include/common/hopscotch-map-2.3.1/tests/utils.h create mode 100644 external/include/common/hopscotch-map-2.3.1/tsl-hopscotch-map.natvis diff --git a/RTEA.vcxproj b/RTEA.vcxproj index d3595784c..669864936 100644 --- a/RTEA.vcxproj +++ b/RTEA.vcxproj @@ -160,7 +160,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) Disabled - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;DEBUG_BUILD;DEBUGMODE;TARGET_MACHINE_X86;%(PreprocessorDefinitions) false EnableFastChecks @@ -212,7 +212,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) MaxSpeed - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;TRACY_ENABLE;TRACY_ON_DEMAND;DEBUG_BUILD;DEBUGMODE;%(PreprocessorDefinitions) false EnableFastChecks @@ -264,7 +264,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) Disabled - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;MIN_DEBUG_BUILD;DEBUGMODE;TARGET_MACHINE_X86;%(PreprocessorDefinitions) false EnableFastChecks @@ -316,7 +316,7 @@ /bigobj /Zm300 %(AdditionalOptions) /bigobj %(AdditionalOptions) Disabled - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;MIN_DEBUG_BUILD;DEBUGMODE;%(PreprocessorDefinitions) false EnableFastChecks @@ -371,7 +371,7 @@ true Speed false - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;DEBUG_RELEASE_BUILD;NDEBUG;TRACY_ENABLE;TRACY_ON_DEMAND;TARGET_MACHINE_X86;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -432,7 +432,7 @@ true Speed false - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;TRACY_ENABLE;TRACY_ON_DEMAND;PROFILING_BUILD;NDEBUG;TARGET_MACHINE_X86;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -493,7 +493,7 @@ true Speed false - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;DEBUG_RELEASE_BUILD;NDEBUG;TRACY_ENABLE;TRACY_ON_DEMAND;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -554,7 +554,7 @@ true Speed true - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;TRACY_ENABLE;TRACY_ON_DEMAND;PROFILING_BUILD;NDEBUG;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -615,7 +615,7 @@ true Speed true - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;RELEASE_BUILD;NDEBUG;LUABIND_NO_ERROR_CHECKING;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_ONLY_LOCALHOST;TRACY_NO_BROADCAST;TARGET_MACHINE_X86;%(PreprocessorDefinitions) Sync MultiThreadedDLL @@ -674,7 +674,7 @@ true Speed true - $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public + $(ProjectDir);$(ProjectDir)System;$(ProjectDir)Lua;$(ProjectDir)Entities;$(ProjectDir)Activities;$(ProjectDir)Managers;$(ProjectDir)Menus;$(ProjectDir)GUI;$(ProjectDir)GUI\Wrappers;$(ProjectDir)external\include\win;$(ProjectDir)external\include\common\;$(ProjectDir)external\include\common\boost_1_75;$(ProjectDir)external\include\win\LZ4;$(ProjectDir)external\include\common\fmod;$(ProjectDir)external\include\common\thread-pool-3.5.0\include;$(ProjectDir)external\include\common\hopscotch-map-2.3.1\include;$(ProjectDir)external\sources\libpng-1.6.40\include;$(ProjectDir)external\sources\zlib-ng-2.1.3\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\include;$(ProjectDir)external\sources\allegro 4.4.3.1-custom\addons\loadpng;$(ProjectDir)external\sources\SDL2-2.26.3\include;$(ProjectDir)external\sources\LuaJIT-2.1\src;$(ProjectDir)external\sources\luabind-0.7.1\;$(ProjectDir)external\sources\luabind-0.7.1\luabind;$(ProjectDir)external\sources\RakNet\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\include;$(ProjectDir)external\sources\minizip-ng-4.0.0\src;$(ProjectDir)external\sources\tracy\public _ITERATOR_DEBUG_LEVEL=0;WIN32_LEAN_AND_MEAN;ALLEGRO_STATICLINK;ALLEGRO_NO_STD_HEADERS;ALLEGRO_NO_MAGIC_MAIN;_CRT_SECURE_NO_DEPRECATE;ZLIB_WINAPI;ZLIB_COMPAT;_WINDOWS;WIN32;RELEASE_BUILD;NDEBUG;LUABIND_NO_ERROR_CHECKING;TRACY_ENABLE;TRACY_ON_DEMAND;TRACY_ONLY_LOCALHOST;TRACY_NO_BROADCAST;%(PreprocessorDefinitions) Sync MultiThreadedDLL diff --git a/System/SpatialPartitionGrid.h b/System/SpatialPartitionGrid.h index 5f37282c4..9cadcf772 100644 --- a/System/SpatialPartitionGrid.h +++ b/System/SpatialPartitionGrid.h @@ -6,6 +6,8 @@ #include "Constants.h" +#include "tsl/hopscotch_set.h" + namespace RTE { class Box; @@ -98,7 +100,7 @@ namespace RTE { std::array>, Activity::MaxTeamCount + 1> m_Cells; //!< Array of cells for each team. The outside-vector is the vector of cells for the team, and each inside-vector entry contains all MOIDs in the cell's space that can collide with that team. std::array>, Activity::MaxTeamCount + 1> m_PhysicsCells; //!< Same as m_Cells, but includes only objects that are GetsHitByMOs. - std::unordered_set m_UsedCellIds; //!< Set of used cell Ids, maintained to avoid wasting time looping through and clearing unused cells. + tsl::hopscotch_set m_UsedCellIds; //!< Set of used cell Ids, maintained to avoid wasting time looping through and clearing unused cells. /// /// Gets the Id of the cell at the given SpatialPartitionGrid coordinates, automatically accounting for wrapping. diff --git a/external/include/common/hopscotch-map-2.3.1/.codecov.yml b/external/include/common/hopscotch-map-2.3.1/.codecov.yml new file mode 100644 index 000000000..92a0a4a07 --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/.codecov.yml @@ -0,0 +1,5 @@ +comment: off +coverage: + status: + project: off + patch: off diff --git a/external/include/common/hopscotch-map-2.3.1/.github/workflows/ci.yml b/external/include/common/hopscotch-map-2.3.1/.github/workflows/ci.yml new file mode 100644 index 000000000..593b05d68 --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/.github/workflows/ci.yml @@ -0,0 +1,136 @@ +name: CI + +on: [push, pull_request, release] + +jobs: + build: + strategy: + fail-fast: false + matrix: + config: + - { + name: linux-x64-gcc, + os: ubuntu-latest, + cxx: g++, + cmake-build-type: Release + } + - { + name: linux-x64-gcc-no-exceptions, + os: ubuntu-latest, + cxx: g++, + cxx-flags: -fno-exceptions, + cmake-build-type: Release + } + - { + name: linux-x64-clang, + os: ubuntu-latest, + cxx: clang++, + cmake-build-type: Release + } + - { + name: macos-x64-gcc, + os: macos-latest, + cxx: g++, + cmake-build-type: Release + } + - { + name: macos-x64-clang, + os: macos-latest, + cxx: clang++, + cmake-build-type: Release + } + - { + name: linux-x64-clang-sanitize, + os: ubuntu-latest, + cxx: clang++, + cxx-flags: "-fsanitize=address,undefined", + cmake-build-type: Release + } + - { + name: linux-x64-gcc-coverage, + os: ubuntu-latest, + cxx: g++, + cxx-flags: --coverage, + gcov-tool: gcov, + cmake-build-type: Debug + } + - { + name: windows-x64-vs-2019, + os: windows-2019, + cmake-build-type: Release, + cmake-generator: Visual Studio 16 2019, + cmake-platform: x64, + vcpkg-triplet: x64-windows-static-md + } + - { + name: windows-x86-vs-2019, + os: windows-2019, + cmake-build-type: Release, + cmake-generator: Visual Studio 16 2019, + cmake-platform: Win32, + vcpkg-triplet: x86-windows-static-md + } + - { + name: windows-x64-vs-2022, + os: windows-2022, + cmake-build-type: Release, + cmake-generator: Visual Studio 17 2022, + cmake-platform: x64, + vcpkg-triplet: x64-windows-static-md + } + - { + name: windows-x86-vs-2022, + os: windows-2022, + cmake-build-type: Release, + cmake-generator: Visual Studio 17 2022, + cmake-platform: Win32, + vcpkg-triplet: x86-windows-static-md + } + name: ${{matrix.config.name}} + runs-on: ${{matrix.config.os}} + steps: + - uses: actions/checkout@v2 + + # Windows + - name: Install boost (Windows) + run: vcpkg install boost-test:${{matrix.config.vcpkg-triplet}} + if: runner.os == 'Windows' + + - name: Configure CMake (Windows) + run: cmake -G "${{matrix.config.cmake-generator}}" -A ${{matrix.config.cmake-platform}} -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=${{matrix.config.vcpkg-triplet}} -S ${{github.workspace}}/tests -B ${{github.workspace}}/build + if: runner.os == 'Windows' + + - name: Build (Windows) + run: cmake --build ${{github.workspace}}/build --config ${{matrix.config.cmake-build-type}} --verbose + if: runner.os == 'Windows' + + - name: Test (Windows) + run: ${{github.workspace}}/build/${{matrix.config.cmake-build-type}}/tsl_hopscotch_map_tests.exe + if: runner.os == 'Windows' + + # Linux or macOS + - name: Install boost (Linux or macOS) + run: vcpkg install boost-test + if: runner.os == 'Linux' || runner.os == 'macOS' + + - name: Configure CMake (Linux or macOS) + run: cmake -DCMAKE_BUILD_TYPE=${{matrix.config.cmake-build-type}} -DCMAKE_TOOLCHAIN_FILE="$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" -S ${{github.workspace}}/tests -B ${{github.workspace}}/build + env: + CXX: ${{matrix.config.cxx}} + CXXFLAGS: ${{matrix.config.cxx-flags}} + if: runner.os == 'Linux' || runner.os == 'macOS' + + - name: Build (Linux or macOS) + run: cmake --build ${{github.workspace}}/build --verbose + if: runner.os == 'Linux' || runner.os == 'macOS' + + - name: Test (Linux or macOS) + run: ${{github.workspace}}/build/tsl_hopscotch_map_tests + if: runner.os == 'Linux' || runner.os == 'macOS' + + - name: Coverage + run: | + sudo apt-get install -y lcov + lcov -c -b ${{github.workspace}}/include -d ${{github.workspace}}/build -o ${{github.workspace}}/coverage.info --no-external --gcov-tool ${{matrix.config.gcov-tool}} + bash <(curl -s https://codecov.io/bash) -f ${{github.workspace}}/coverage.info + if: ${{matrix.config.name == 'linux-x64-gcc-coverage'}} diff --git a/external/include/common/hopscotch-map-2.3.1/CMakeLists.txt b/external/include/common/hopscotch-map-2.3.1/CMakeLists.txt new file mode 100644 index 000000000..6fea27993 --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/CMakeLists.txt @@ -0,0 +1,80 @@ +cmake_minimum_required(VERSION 3.1) +include(GNUInstallDirs) + + +project(tsl-hopscotch-map VERSION 2.3.1) + +add_library(hopscotch_map INTERFACE) +# Use tsl::hopscotch_map as target, more consistent with other libraries conventions (Boost, Qt, ...) +add_library(tsl::hopscotch_map ALIAS hopscotch_map) + +target_include_directories(hopscotch_map INTERFACE + "$" + "$") + +list(APPEND headers "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/bhopscotch_map.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/bhopscotch_set.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/hopscotch_growth_policy.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/hopscotch_hash.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/hopscotch_map.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl/hopscotch_set.h") +target_sources(hopscotch_map INTERFACE "$") + +if(MSVC) + target_sources(hopscotch_map INTERFACE + "$" + "$") +endif() + + + + +# Installation (only compatible with CMake version >= 3.3) +if(${CMAKE_VERSION} VERSION_GREATER "3.2") + include(CMakePackageConfigHelpers) + + ## Install include directory and potential natvis file + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/tsl" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + + if(MSVC) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/tsl-hopscotch-map.natvis" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}") + endif() + + + + ## Create and install tsl-hopscotch-mapConfig.cmake + configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/tsl-hopscotch-mapConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/tsl-hopscotch-mapConfig.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-hopscotch-map") + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tsl-hopscotch-mapConfig.cmake" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-hopscotch-map") + + + + ## Create and install tsl-hopscotch-mapTargets.cmake + install(TARGETS hopscotch_map + EXPORT tsl-hopscotch-mapTargets) + + install(EXPORT tsl-hopscotch-mapTargets + NAMESPACE tsl:: + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-hopscotch-map") + + + + ## Create and install tsl-hopscotch-mapConfigVersion.cmake + # tsl-hopscotch-map is header-only and does not depend on the architecture. + # Remove CMAKE_SIZEOF_VOID_P from tsl-hopscotch-mapConfigVersion.cmake so that a + # tsl-hopscotch-mapConfig.cmake generated for a 64 bit target can be used for 32 bit + # targets and vice versa. + set(CMAKE_SIZEOF_VOID_P_BACKUP ${CMAKE_SIZEOF_VOID_P}) + unset(CMAKE_SIZEOF_VOID_P) + write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/tsl-hopscotch-mapConfigVersion.cmake" + COMPATIBILITY SameMajorVersion) + set(CMAKE_SIZEOF_VOID_P ${CMAKE_SIZEOF_VOID_P_BACKUP}) + + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/tsl-hopscotch-mapConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/cmake/tsl-hopscotch-map") +endif() diff --git a/external/include/common/hopscotch-map-2.3.1/LICENSE b/external/include/common/hopscotch-map-2.3.1/LICENSE new file mode 100644 index 000000000..b360b6daa --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Thibaut Goetghebuer-Planchon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/include/common/hopscotch-map-2.3.1/README.md b/external/include/common/hopscotch-map-2.3.1/README.md new file mode 100644 index 000000000..026822141 --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/README.md @@ -0,0 +1,342 @@ +[![CI](https://github.com/Tessil/hopscotch-map/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Tessil/hopscotch-map/actions/workflows/ci.yml) + +## A C++ implementation of a fast hash map and hash set using hopscotch hashing + +The hopscotch-map library is a C++ implementation of a fast hash map and hash set using open-addressing and hopscotch hashing to resolve collisions. It is a cache-friendly data structure offering better performances than `std::unordered_map` in most cases and is closely similar to `google::dense_hash_map` while using less memory and providing more functionalities. + +The library provides the following main classes: `tsl::hopscotch_map`, `tsl::hopscotch_set`, `tsl::hopscotch_pg_map` and `tsl::hopscotch_pg_set`. The first two are faster and use a power of two growth policy, the last two use a prime growth policy instead and are able to cope better with a poor hash function. Use the prime version if there is a chance of repeating patterns in the lower bits of your hash (e.g. you are storing pointers with an identity hash function). See [GrowthPolicy](#growth-policy) for details. + + +In addition to these classes the library also provides `tsl::bhopscotch_map`, `tsl::bhopscotch_set`, `tsl::bhopscotch_pg_map` and `tsl::bhopscotch_pg_set`. These classes have an additional requirement for the key, it must be `LessThanComparable`, but they provide a better asymptotic upper bound, see [details](#deny-of-service-dos-attack) in example. Nonetheless if you don't have specific requirements (risk of hash DoS attacks), `tsl::hopscotch_map` and `tsl::hopscotch_set` should be sufficient in most cases and should be your default pick as they perform better in general. + + +An overview of hopscotch hashing and some implementation details can be found [here](https://tessil.github.io/2016/08/29/hopscotch-hashing.html). + +A **benchmark** of `tsl::hopscotch_map` against other hash maps may be found [here](https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html). This page also gives some advices on which hash table structure you should try for your use case (useful if you are a bit lost with the multiple hash tables implementations in the `tsl` namespace). + +### Key features +- Header-only library, just add the [include](include/) directory to your include path and you are ready to go. If you use CMake, you can also use the `tsl::hopscotch_map` exported target from the [CMakeLists.txt](CMakeLists.txt). +- Fast hash table, see [benchmark](https://tessil.github.io/2016/08/29/benchmark-hopscotch-map.html) for some numbers. +- Support for move-only and non-default constructible key/value. +- Support for heterogeneous lookups allowing the usage of `find` with a type different than `Key` (e.g. if you have a map that uses `std::unique_ptr` as key, you can use a `foo*` or a `std::uintptr_t` as key parameter to `find` without constructing a `std::unique_ptr`, see [example](#heterogeneous-lookups)). +- No need to reserve any sentinel value from the keys. +- Possibility to store the hash value on insert for faster rehash and lookup if the hash or the key equal functions are expensive to compute (see the [StoreHash](https://tessil.github.io/hopscotch-map/classtsl_1_1hopscotch__map.html#details) template parameter). +- If the hash is known before a lookup, it is possible to pass it as parameter to speed-up the lookup (see `precalculated_hash` parameter in [API](https://tessil.github.io/hopscotch-map/classtsl_1_1hopscotch__map.html#a74d83c67c50bc8385bb11f78142eaa86)). +- The `tsl::bhopscotch_map` and `tsl::bhopscotch_set` provide a worst-case of O(log n) on lookups and deletions making these classes resistant to hash table Deny of Service (DoS) attacks (see [details](#deny-of-service-dos-attack) in example). +- The library can be used with exceptions disabled (through `-fno-exceptions` option on Clang and GCC, without an `/EH` option on MSVC or simply by defining `TSL_NO_EXCEPTIONS`). `std::terminate` is used in replacement of the `throw` instruction when exceptions are disabled. +- API closely similar to `std::unordered_map` and `std::unordered_set`. + +### Differences compared to `std::unordered_map` +`tsl::hopscotch_map` tries to have an interface similar to `std::unordered_map`, but some differences exist. +- Iterator invalidation on insert doesn't behave in the same way. In general any operation modifying the hash table, except `erase`, invalidate all the iterators (see [API](https://tessil.github.io/hopscotch-map/classtsl_1_1hopscotch__map.html#details) for details). +- References and pointers to keys or values in the map are invalidated in the same way as iterators to these keys-values on insert. +- For iterators, `operator*()` and `operator->()` return a reference and a pointer to `const std::pair` instead of `std::pair`, making the value `T` not modifiable. To modify the value you have to call the `value()` method of the iterator to get a mutable reference. Example: +```c++ +tsl::hopscotch_map map = {{1, 1}, {2, 1}, {3, 1}}; +for(auto it = map.begin(); it != map.end(); ++it) { + //it->second = 2; // Illegal + it.value() = 2; // Ok +} +``` +- Move-only types must have a nothrow move constructor (with open addressing, it is not possible to keep the strong exception guarantee on rehash if the move constructor may throw). +- No support for some buckets related methods (like `bucket_size`, `bucket`, ...). + +These differences also apply between `std::unordered_set` and `tsl::hopscotch_set`. + +Thread-safety and exceptions guarantees are the same as `std::unordered_map/set` (i.e. possible to have multiple readers with no writer). + +### Growth policy + +The library supports multiple growth policies through the `GrowthPolicy` template parameter. Three policies are provided by the library but you can easily implement your own if needed. + +* **[tsl::hh::power_of_two_growth_policy.](https://tessil.github.io/hopscotch-map/classtsl_1_1hh_1_1power__of__two__growth__policy.html)** Default policy used by `tsl::(b)hopscotch_map/set`. This policy keeps the size of the bucket array of the hash table to a power of two. This constraint allows the policy to avoid the usage of the slow modulo operation to map a hash to a bucket, instead of hash % 2n, it uses hash & (2n - 1) (see [fast modulo](https://en.wikipedia.org/wiki/Modulo_operation#Performance_issues)). Fast but this may cause a lot of collisions with a poor hash function as the modulo with a power of two only masks the most significant bits in the end. +* **[tsl::hh::prime_growth_policy.](https://tessil.github.io/hopscotch-map/classtsl_1_1hh_1_1prime__growth__policy.html)** Default policy used by `tsl::(b)hopscotch_pg_map/set`. The policy keeps the size of the bucket array of the hash table to a prime number. When mapping a hash to a bucket, using a prime number as modulo will result in a better distribution of the hashes across the buckets even with a poor hash function. To allow the compiler to optimize the modulo operation, the policy use a lookup table with constant primes modulos (see [API](https://tessil.github.io/hopscotch-map/classtsl_1_1hh_1_1prime__growth__policy.html#details) for details). Slower than `tsl::hh::power_of_two_growth_policy` but more secure. +* **[tsl::hh::mod_growth_policy.](https://tessil.github.io/hopscotch-map/classtsl_1_1hh_1_1mod__growth__policy.html)** The policy grows the map by a customizable growth factor passed in parameter. It then just use the modulo operator to map a hash to a bucket. Slower but more flexible. + +If you encounter poor performances check the `overflow_size()`, if it is not zero you may have a lot of hash collisions. Either change the hash function for something more uniform or try another growth policy (mainly `tsl::hh::prime_growth_policy`). Unfortunately it is sometimes difficult to guard yourself against collisions (e.g. DoS attack on the hash map). If needed, check also `tsl::bhopscotch_map/set` which offer a worst-case scenario of O(log n) on lookups instead of O(n), see [details](#deny-of-service-dos-attack) in example. + +To implement your own policy, you have to implement the following interface. + +```c++ +struct custom_policy { + // Called on hash table construction and rehash, min_bucket_count_in_out is the minimum buckets + // that the hash table needs. The policy can change it to a higher number of buckets if needed + // and the hash table will use this value as bucket count. If 0 bucket is asked, then the value + // must stay at 0. + explicit custom_policy(std::size_t& min_bucket_count_in_out); + + // Return the bucket [0, bucket_count()) to which the hash belongs. + // If bucket_count() is 0, it must always return 0. + std::size_t bucket_for_hash(std::size_t hash) const noexcept; + + // Return the number of buckets that should be used on next growth + std::size_t next_bucket_count() const; + + // Maximum number of buckets supported by the policy + std::size_t max_bucket_count() const; + + // Reset the growth policy as if the policy was created with a bucket count of 0. + // After a clear, the policy must always return 0 when bucket_for_hash() is called. + void clear() noexcept; +} +``` +### Installation +To use hopscotch-map, just add the [include](include/) directory to your include path. It is a **header-only** library. + +If you use CMake, you can also use the `tsl::hopscotch_map` exported target from the [CMakeLists.txt](CMakeLists.txt) with `target_link_libraries`. +```cmake +# Example where the hopscotch-map project is stored in a third-party directory +add_subdirectory(third-party/hopscotch-map) +target_link_libraries(your_target PRIVATE tsl::hopscotch_map) +``` + +If the project has been installed through `make install`, you can also use `find_package(tsl-hopscotch-map REQUIRED)` instead of `add_subdirectory`. + +The code should work with any C++11 standard-compliant compiler and has been tested with GCC 4.8.4, Clang 3.5.0 and Visual Studio 2015. + +To run the tests you will need the Boost Test library and CMake. + +```bash +git clone https://github.com/Tessil/hopscotch-map.git +cd hopscotch-map/tests +mkdir build +cd build +cmake .. +cmake --build . +./tsl_hopscotch_map_tests +``` + + +### Usage +The API can be found [here](https://tessil.github.io/hopscotch-map/). + +All methods are not documented yet, but they replicate the behaviour of the ones in `std::unordered_map` and `std::unordered_set`, except if specified otherwise. + +### Example + +```c++ +#include +#include +#include +#include +#include + +int main() { + tsl::hopscotch_map map = {{"a", 1}, {"b", 2}}; + map["c"] = 3; + map["d"] = 4; + + map.insert({"e", 5}); + map.erase("b"); + + for(auto it = map.begin(); it != map.end(); ++it) { + //it->second += 2; // Not valid. + it.value() += 2; + } + + // {d, 6} {a, 3} {e, 7} {c, 5} + for(const auto& key_value : map) { + std::cout << "{" << key_value.first << ", " << key_value.second << "}" << std::endl; + } + + + if(map.find("a") != map.end()) { + std::cout << "Found \"a\"." << std::endl; + } + + const std::size_t precalculated_hash = std::hash()("a"); + // If we already know the hash beforehand, we can pass it in parameter to speed-up lookups. + if(map.find("a", precalculated_hash) != map.end()) { + std::cout << "Found \"a\" with hash " << precalculated_hash << "." << std::endl; + } + + + /* + * Calculating the hash and comparing two std::string may be slow. + * We can store the hash of each std::string in the hash map to make + * the inserts and lookups faster by setting StoreHash to true. + */ + tsl::hopscotch_map, + std::equal_to, + std::allocator>, + 30, true> map2; + + map2["a"] = 1; + map2["b"] = 2; + + // {a, 1} {b, 2} + for(const auto& key_value : map2) { + std::cout << "{" << key_value.first << ", " << key_value.second << "}" << std::endl; + } + + + + + tsl::hopscotch_set set; + set.insert({1, 9, 0}); + set.insert({2, -1, 9}); + + // {0} {1} {2} {9} {-1} + for(const auto& key : set) { + std::cout << "{" << key << "}" << std::endl; + } +} +``` + +#### Heterogeneous lookups + +Heterogeneous overloads allow the usage of other types than `Key` for lookup and erase operations as long as the used types are hashable and comparable to `Key`. + +To activate the heterogeneous overloads in `tsl::hopscotch_map/set`, the qualified-id `KeyEqual::is_transparent` must be valid. It works the same way as for [`std::map::find`](http://en.cppreference.com/w/cpp/container/map/find). You can either use [`std::equal_to<>`](http://en.cppreference.com/w/cpp/utility/functional/equal_to_void) or define your own function object. + +Both `KeyEqual` and `Hash` will need to be able to deal with the different types. + +```c++ +#include +#include +#include +#include + + +struct employee { + employee(int id, std::string name) : m_id(id), m_name(std::move(name)) { + } + + // Either we include the comparators in the class and we use `std::equal_to<>`... + friend bool operator==(const employee& empl, int empl_id) { + return empl.m_id == empl_id; + } + + friend bool operator==(int empl_id, const employee& empl) { + return empl_id == empl.m_id; + } + + friend bool operator==(const employee& empl1, const employee& empl2) { + return empl1.m_id == empl2.m_id; + } + + + int m_id; + std::string m_name; +}; + +// ... or we implement a separate class to compare employees. +struct equal_employee { + using is_transparent = void; + + bool operator()(const employee& empl, int empl_id) const { + return empl.m_id == empl_id; + } + + bool operator()(int empl_id, const employee& empl) const { + return empl_id == empl.m_id; + } + + bool operator()(const employee& empl1, const employee& empl2) const { + return empl1.m_id == empl2.m_id; + } +}; + +struct hash_employee { + std::size_t operator()(const employee& empl) const { + return std::hash()(empl.m_id); + } + + std::size_t operator()(int id) const { + return std::hash()(id); + } +}; + + +int main() { + // Use std::equal_to<> which will automatically deduce and forward the parameters + tsl::hopscotch_map> map; + map.insert({employee(1, "John Doe"), 2001}); + map.insert({employee(2, "Jane Doe"), 2002}); + map.insert({employee(3, "John Smith"), 2003}); + + // John Smith 2003 + auto it = map.find(3); + if(it != map.end()) { + std::cout << it->first.m_name << " " << it->second << std::endl; + } + + map.erase(1); + + + + // Use a custom KeyEqual which has an is_transparent member type + tsl::hopscotch_map map2; + map2.insert({employee(4, "Johnny Doe"), 2004}); + + // 2004 + std::cout << map2.at(4) << std::endl; +} +``` + +#### Deny of Service (DoS) attack +In addition to `tsl::hopscotch_map` and `tsl::hopscotch_set`, the library provides two more "secure" options: `tsl::bhopscotch_map` and `tsl::bhopscotch_set` (all with their `pg` counterparts). + +These two additions have a worst-case asymptotic complexity of O(log n) for lookups and deletions and an amortized worst case of O(log n) for insertions (amortized due to the possibility of rehash which would be in O(n)). Even if the hash function maps all the elements to the same bucket, the O(log n) would still hold. + +This provides a security against hash table Deny of Service (DoS) attacks. + +To achieve this, the *secure* versions use a binary search tree for the overflown elements (see [implementation details](https://tessil.github.io/2016/08/29/hopscotch-hashing.html)) and thus need the elements to be `LessThanComparable`. An additional `Compare` template parameter is needed. + +```c++ +#include +#include +#include +#include +#include + +/* + * Poor hash function which always returns 1 to simulate + * a Deny of Service attack. + */ +struct dos_attack_simulation_hash { + std::size_t operator()(int id) const { + return 1; + } +}; + +int main() { + /* + * Slow due to the hash function, insertions are done in O(n). + */ + tsl::hopscotch_map map; + + auto start = std::chrono::high_resolution_clock::now(); + for(int i=0; i < 10000; i++) { + map.insert({i, 0}); + } + auto end = std::chrono::high_resolution_clock::now(); + + // 110 ms + auto duration = std::chrono::duration_cast(end-start); + std::cout << duration.count() << " ms" << std::endl; + + + + + /* + * Faster. Even with the poor hash function, insertions end-up to + * be O(log n) in average (and O(n) when a rehash occurs). + */ + tsl::bhopscotch_map map_secure; + + start = std::chrono::high_resolution_clock::now(); + for(int i=0; i < 10000; i++) { + map_secure.insert({i, 0}); + } + end = std::chrono::high_resolution_clock::now(); + + // 2 ms + duration = std::chrono::duration_cast(end-start); + std::cout << duration.count() << " ms" << std::endl; +} +``` + +### License + +The code is licensed under the MIT license, see the [LICENSE file](LICENSE) for details. diff --git a/external/include/common/hopscotch-map-2.3.1/cmake/tsl-hopscotch-mapConfig.cmake.in b/external/include/common/hopscotch-map-2.3.1/cmake/tsl-hopscotch-mapConfig.cmake.in new file mode 100644 index 000000000..16226759b --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/cmake/tsl-hopscotch-mapConfig.cmake.in @@ -0,0 +1,9 @@ +# This module sets the following variables: +# * tsl-hopscotch-map_FOUND - true if tsl-hopscotch-map found on the system +# * tsl-hopscotch-map_INCLUDE_DIRS - the directory containing tsl-hopscotch-map headers +@PACKAGE_INIT@ + +if(NOT TARGET tsl::hopscotch_map) + include("${CMAKE_CURRENT_LIST_DIR}/tsl-hopscotch-mapTargets.cmake") + get_target_property(tsl-hopscotch-map_INCLUDE_DIRS tsl::hopscotch_map INTERFACE_INCLUDE_DIRECTORIES) +endif() diff --git a/external/include/common/hopscotch-map-2.3.1/doxygen.conf b/external/include/common/hopscotch-map-2.3.1/doxygen.conf new file mode 100644 index 000000000..5cd56fbc9 --- /dev/null +++ b/external/include/common/hopscotch-map-2.3.1/doxygen.conf @@ -0,0 +1,2486 @@ +# Doxyfile 1.8.11 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = hopscotch-map + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doc/ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = include/tsl/ README.md + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, +# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.as \ + *.js + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = \ +tsl::detail_hopscotch_hash::has_is_transparent* \ +tsl::detail_hopscotch_hash::has_key_compare* \ +tsl::detail_hopscotch_hash::make_void \ +tsl::detail_hopscotch_hash::is_power_of_two_policy* \ +tsl::detail_hopscotch_hash::smallest_type_for_min_bits \ +tsl::detail_hopscotch_hash::hopscotch_bucket_hash* \ +tsl::detail_hopscotch_hash::hopscotch_bucket + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /