Compare commits
40 commits
Author | SHA1 | Date | |
---|---|---|---|
Stephen Seo | 7636f89c82 | ||
Stephen Seo | ad933b157b | ||
Stephen Seo | 654ddac943 | ||
Stephen Seo | 599ebca196 | ||
Stephen Seo | 222639ec3a | ||
Stephen Seo | 0b0c1eb213 | ||
Stephen Seo | a3cd26f529 | ||
Stephen Seo | 6c03859cf1 | ||
Stephen Seo | 57101d283d | ||
Stephen Seo | cc95c9758e | ||
Stephen Seo | 033a455e3e | ||
Stephen Seo | b4b0802e7c | ||
Stephen Seo | 2745362e41 | ||
Stephen Seo | 6e07bb1877 | ||
Stephen Seo | c245b438a7 | ||
Stephen Seo | a879e0ef8c | ||
Stephen Seo | 62600cbfa6 | ||
Stephen Seo | 292bffb636 | ||
Stephen Seo | 3286aa5a74 | ||
Stephen Seo | 841a591aa4 | ||
Stephen Seo | e61d2724e6 | ||
Stephen Seo | f27c22675a | ||
Stephen Seo | e0a18900e4 | ||
Stephen Seo | 51bea5a40f | ||
Stephen Seo | 7b512958fd | ||
Stephen Seo | 79748d58f1 | ||
Stephen Seo | 388d48056f | ||
Stephen Seo | 577a647ca4 | ||
Stephen Seo | 120368094e | ||
Stephen Seo | c2dc5124c9 | ||
Stephen Seo | 64e9b18f89 | ||
Stephen Seo | 222fbb0862 | ||
Stephen Seo | 4fd463a870 | ||
Stephen Seo | 5c72ecb74b | ||
Stephen Seo | beb3eae6e2 | ||
Stephen Seo | e8f5e57772 | ||
Stephen Seo | a18ab3be31 | ||
Stephen Seo | 1069216c1a | ||
Stephen Seo | a851c63619 | ||
Stephen Seo | be43cd03c5 |
212
.clang-format
Normal file
212
.clang-format
Normal file
|
@ -0,0 +1,212 @@
|
|||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: Google
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveMacros: None
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: None
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
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: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: true
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<ext/.*\.h>'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseLabels: true
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentRequires: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Never
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
- Language: TextProto
|
||||
Delimiters:
|
||||
- pb
|
||||
- PB
|
||||
- proto
|
||||
- PROTO
|
||||
EnclosingFunctions:
|
||||
- EqualsProto
|
||||
- EquivToProto
|
||||
- PARSE_PARTIAL_TEXT_PROTO
|
||||
- PARSE_TEST_PROTO
|
||||
- PARSE_TEXT_PROTO
|
||||
- ParseTextOrDie
|
||||
- ParseTextProtoOrDie
|
||||
- ParseTestProto
|
||||
- ParsePartialTestProto
|
||||
CanonicalDelimiter: pb
|
||||
BasedOnStyle: google
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
BitFieldColonSpacing: Both
|
||||
Standard: Auto
|
||||
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
|
||||
...
|
||||
|
14
.forgejo/workflows/doxygen.yaml
Normal file
14
.forgejo/workflows/doxygen.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Publish doxygen documentation to seodisparate.com
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
doxygen-gen-and-publish:
|
||||
runs-on: archLinux
|
||||
steps:
|
||||
- run: git clone --depth=1 --no-single-branch https://git.seodisparate.com/stephenseo/EntityComponentMetaSystem.git ECMS
|
||||
- run: cd ECMS && git checkout master
|
||||
- run: cd ECMS && doxygen
|
||||
- run: rsync -r --delete ECMS/doxygen_html/html/ /srv/http/ECMS_docs/
|
14
.forgejo/workflows/unittests.yml
Normal file
14
.forgejo/workflows/unittests.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Run UnitTests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-and-run-unittests:
|
||||
runs-on: any_archLinux
|
||||
steps:
|
||||
- run: git clone --depth=1 --no-single-branch https://git.seodisparate.com/stephenseo/EntityComponentMetaSystem.git ECMS
|
||||
- run: cd ECMS && git checkout $GITHUB_REF_NAME
|
||||
- run: cd ECMS && cmake -S src -B buildDebug -DCMAKE_BUILD_TYPE=Debug
|
||||
- run: make -C ECMS/buildDebug && ./ECMS/buildDebug/UnitTests
|
26
.github/workflows/gh-pages.yml
vendored
Normal file
26
.github/workflows/gh-pages.yml
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
name: GitHub Pages Generated Doxygen Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-deploy-doxygen-docs:
|
||||
runs-on: ubuntu-latest
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Generate Doxygen Documentation
|
||||
uses: mattnotmitt/doxygen-action@v1
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./doxygen_html/html
|
18
.github/workflows/unittests.yml
vendored
Normal file
18
.github/workflows/unittests.yml
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
name: Run UnitTests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build-and-run-unittests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: sudo /usr/bin/env DEBIAN_FRONTEND=noninteractive apt-get install libgtest-dev cmake
|
||||
- name: Get sources
|
||||
run: git clone --depth=1 --no-single-branch https://github.com/Stephen-Seo/EntityComponentMetaSystem.git ECMS && cd ECMS && git checkout $GITHUB_REF_NAME
|
||||
- name: Build UnitTests
|
||||
run: cd ECMS && cmake -S src -B buildDebug -DCMAKE_BUILD_TYPE=Debug && make -C buildDebug
|
||||
- name: Run UnitTests
|
||||
run: ./ECMS/buildDebug/UnitTests
|
4
Doxyfile
4
Doxyfile
|
@ -771,7 +771,7 @@ WARN_LOGFILE =
|
|||
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = ./src/EC/Meta ./src/EC
|
||||
INPUT = ./src/EC/Meta ./src/EC ./doxygen/mainpage.dox ./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
|
||||
|
@ -796,7 +796,7 @@ INPUT_ENCODING = UTF-8
|
|||
# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl,
|
||||
# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js.
|
||||
|
||||
FILE_PATTERNS = *.hpp
|
||||
FILE_PATTERNS = *.hpp *.md
|
||||
|
||||
# The RECURSIVE tag can be used to specify whether or not subdirectories should
|
||||
# be searched for input files as well.
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2019,2021 Stephen Seo
|
||||
Copyright (c) 2016-2019,2021-2022 Stephen Seo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
32
README.md
32
README.md
|
@ -1,16 +1,40 @@
|
|||
# About
|
||||
|
||||
# Compiling
|
||||
EntityComponentMetaSystem is a header-only library. However, UnitTests can be
|
||||
built and run using cmake (gtest is a dependency).
|
||||
|
||||
[(Note that gtest uses the BSD 3-Clause License.)](https://github.com/google/googletest/blob/master/LICENSE)
|
||||
|
||||
# Generated Doxygen Documentation
|
||||
|
||||
[Check this repository's gh-pages documentation on ECMS](https://stephen-seo.github.io/EntityComponentMetaSystem/)
|
||||
|
||||
[Alternatively, check out the doxygen docs hosted on my website](https://seodisparate.com/ecms_docs/)
|
||||
|
||||
# Compiling the UnitTests
|
||||
|
||||
Create a build directory.
|
||||
`mkdir build`
|
||||
|
||||
Generate makefile with CMake.
|
||||
`cd build`
|
||||
`cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=True ../src`
|
||||
`cmake -DCMAKE_BUILD_TYPE=Debug ../src`
|
||||
|
||||
Build the project.
|
||||
Build the project's UnitTests.
|
||||
`make`
|
||||
|
||||
Optionally install the project to where you want to.
|
||||
Run the UnitTests.
|
||||
`./UnitTests`
|
||||
|
||||
# Install the Header-Only Library
|
||||
|
||||
`mkdir build; cd build`
|
||||
`cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release ../src`
|
||||
|
||||
Install the project to where you want to.
|
||||
`make DESTDIR=install_here install`
|
||||
|
||||
In this example, `CMAKE_INSTALL_PREFIX=/usr`, then invoking
|
||||
`make DESTDIR=install_here install` will install the `src/EC` directory to
|
||||
`install_here/usr/include`. The path to `Manager.hpp` will then look like
|
||||
`install_here/usr/include/EC/Manager.hpp`
|
||||
|
|
9
doxygen/mainpage.dox
Normal file
9
doxygen/mainpage.dox
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*!
|
||||
\mainpage EntityComponentMetaSystem Index Page
|
||||
|
||||
\ref md_README
|
||||
|
||||
<a href="annotated.html">Classes</a>
|
||||
|
||||
\ref EC::Manager "The Manager class that manages an Entity Component System"
|
||||
*/
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
project(EntityComponentSystem)
|
||||
|
||||
set(EntityComponentSystem_HEADERS
|
||||
|
|
3579
src/EC/Manager.hpp
3579
src/EC/Manager.hpp
File diff suppressed because it is too large
Load diff
|
@ -1,169 +1,161 @@
|
|||
#ifndef EC_META_SYSTEM_THREADPOOL_HPP
|
||||
#define EC_META_SYSTEM_THREADPOOL_HPP
|
||||
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
namespace EC {
|
||||
|
||||
namespace Internal {
|
||||
using TPFnType = std::function<void(void*)>;
|
||||
using TPTupleType = std::tuple<TPFnType, void*>;
|
||||
using TPQueueType = std::queue<TPTupleType>;
|
||||
} // namespace Internal
|
||||
using TPFnType = std::function<void(void *)>;
|
||||
using TPTupleType = std::tuple<TPFnType, void *>;
|
||||
using TPQueueType = std::queue<TPTupleType>;
|
||||
using ThreadPtr = std::unique_ptr<std::thread>;
|
||||
using ThreadStackType = std::vector<std::tuple<ThreadPtr, std::thread::id>>;
|
||||
using ThreadStacksType = std::deque<ThreadStackType>;
|
||||
using ThreadStacksMutexesT = std::deque<std::mutex>;
|
||||
using ThreadCountersT = std::deque<std::atomic_uint>;
|
||||
using PtrsHoldT = std::deque<std::atomic_bool>;
|
||||
using PointersT = std::tuple<ThreadStackType *, std::mutex *,
|
||||
std::atomic_uint *, std::atomic_bool *>;
|
||||
} // namespace Internal
|
||||
|
||||
/*!
|
||||
\brief Implementation of a Thread Pool.
|
||||
|
||||
Note that if SIZE is less than 2, then ThreadPool will not create threads and
|
||||
run queued functions on the calling thread.
|
||||
Note that MAXSIZE template parameter determines how many threads are created
|
||||
each time that startThreads() (or easyStartAndWait()) is called.
|
||||
*/
|
||||
template <unsigned int SIZE>
|
||||
template <unsigned int MAXSIZE>
|
||||
class ThreadPool {
|
||||
public:
|
||||
using THREADCOUNT = std::integral_constant<int, SIZE>;
|
||||
|
||||
ThreadPool() : waitCount(0) {
|
||||
isAlive.store(true);
|
||||
if(SIZE >= 2) {
|
||||
for(unsigned int i = 0; i < SIZE; ++i) {
|
||||
threads.emplace_back([] (std::atomic_bool *isAlive,
|
||||
std::condition_variable *cv,
|
||||
std::mutex *cvMutex,
|
||||
Internal::TPQueueType *fnQueue,
|
||||
std::mutex *queueMutex,
|
||||
int *waitCount,
|
||||
std::mutex *waitCountMutex) {
|
||||
bool hasFn = false;
|
||||
Internal::TPTupleType fnTuple;
|
||||
while(isAlive->load()) {
|
||||
hasFn = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*queueMutex);
|
||||
if(!fnQueue->empty()) {
|
||||
fnTuple = fnQueue->front();
|
||||
fnQueue->pop();
|
||||
hasFn = true;
|
||||
}
|
||||
}
|
||||
if(hasFn) {
|
||||
std::get<0>(fnTuple)(std::get<1>(fnTuple));
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*waitCountMutex);
|
||||
*waitCount += 1;
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(*cvMutex);
|
||||
cv->wait(lock);
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*waitCountMutex);
|
||||
*waitCount -= 1;
|
||||
}
|
||||
}
|
||||
}, &isAlive, &cv, &cvMutex, &fnQueue, &queueMutex, &waitCount,
|
||||
&waitCountMutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
public:
|
||||
ThreadPool()
|
||||
: threadStacks{}, threadStackMutexes{}, fnQueue{}, queueMutex{} {}
|
||||
|
||||
~ThreadPool() {
|
||||
if(SIZE >= 2) {
|
||||
isAlive.store(false);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
cv.notify_all();
|
||||
for(auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
while (!isNotRunning()) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(30));
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Queues a function to be called (doesn't start calling yet).
|
||||
|
||||
To run the queued functions, wakeThreads() must be called to wake the
|
||||
To run the queued functions, startThreads() must be called to wake the
|
||||
waiting threads which will start pulling functions from the queue to be
|
||||
called.
|
||||
|
||||
Note that the easyStartAndWait() calls startThreads() and waits until
|
||||
the threads have finished execution.
|
||||
*/
|
||||
void queueFn(std::function<void(void*)>&& fn, void *ud = nullptr) {
|
||||
void queueFn(std::function<void(void *)> &&fn, void *ud = nullptr) {
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
fnQueue.emplace(std::make_tuple(fn, ud));
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Wakes waiting threads to start running queued functions.
|
||||
\brief Creates MAXSIZE threads that will process queueFn() functions.
|
||||
|
||||
If SIZE is less than 2, then this function call will block until all the
|
||||
queued functions have been executed on the calling thread.
|
||||
Note that if MAXSIZE < 2, then this function will synchronously execute
|
||||
the queued functions and block until the functions have been executed.
|
||||
Otherwise, this function may return before the queued functions have
|
||||
been executed.
|
||||
*/
|
||||
Internal::PointersT startThreads() {
|
||||
if (MAXSIZE >= 2) {
|
||||
checkStacks();
|
||||
auto pointers = newStackEntry();
|
||||
Internal::ThreadStackType *threadStack = std::get<0>(pointers);
|
||||
std::mutex *threadStackMutex = std::get<1>(pointers);
|
||||
std::atomic_uint *aCounter = std::get<2>(pointers);
|
||||
for (unsigned int i = 0; i < MAXSIZE; ++i) {
|
||||
std::thread *newThread = new std::thread(
|
||||
[](Internal::ThreadStackType *threadStack,
|
||||
std::mutex *threadStackMutex,
|
||||
Internal::TPQueueType *fnQueue, std::mutex *queueMutex,
|
||||
std::atomic_uint *initCount) {
|
||||
// add id to idStack "call stack"
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*threadStackMutex);
|
||||
threadStack->push_back(
|
||||
{Internal::ThreadPtr(nullptr),
|
||||
std::this_thread::get_id()});
|
||||
}
|
||||
|
||||
If SIZE is 2 or greater, then this function will return immediately after
|
||||
waking one or all threads, depending on the given boolean parameter.
|
||||
*/
|
||||
void wakeThreads(bool wakeAll = true) {
|
||||
if(SIZE >= 2) {
|
||||
// wake threads to pull functions from queue and run them
|
||||
if(wakeAll) {
|
||||
cv.notify_all();
|
||||
} else {
|
||||
cv.notify_one();
|
||||
++(*initCount);
|
||||
|
||||
// fetch queued fns and execute them
|
||||
// fnTuples must live until end of function
|
||||
std::list<Internal::TPTupleType> fnTuples;
|
||||
do {
|
||||
bool fnFound = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*queueMutex);
|
||||
if (!fnQueue->empty()) {
|
||||
fnTuples.emplace_back(
|
||||
std::move(fnQueue->front()));
|
||||
fnQueue->pop();
|
||||
fnFound = true;
|
||||
}
|
||||
}
|
||||
if (fnFound) {
|
||||
std::get<0>(fnTuples.back())(
|
||||
std::get<1>(fnTuples.back()));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
|
||||
// pop id from idStack "call stack"
|
||||
do {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::microseconds(15));
|
||||
if (initCount->load() != MAXSIZE) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(
|
||||
*threadStackMutex);
|
||||
if (std::get<1>(threadStack->back()) ==
|
||||
std::this_thread::get_id()) {
|
||||
if (!std::get<0>(threadStack->back())) {
|
||||
continue;
|
||||
}
|
||||
std::get<0>(threadStack->back())->detach();
|
||||
threadStack->pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
},
|
||||
threadStack, threadStackMutex, &fnQueue, &queueMutex,
|
||||
aCounter);
|
||||
// Wait until thread has pushed to threadStack before setting
|
||||
// the handle to it
|
||||
while (aCounter->load() != i + 1) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(15));
|
||||
}
|
||||
std::lock_guard<std::mutex> stackLock(*threadStackMutex);
|
||||
std::get<0>(threadStack->at(i)).reset(newThread);
|
||||
}
|
||||
return pointers;
|
||||
} else {
|
||||
// pull functions from queue and run them on main thread
|
||||
Internal::TPTupleType fnTuple;
|
||||
bool hasFn;
|
||||
do {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
if(!fnQueue.empty()) {
|
||||
hasFn = true;
|
||||
fnTuple = fnQueue.front();
|
||||
fnQueue.pop();
|
||||
} else {
|
||||
hasFn = false;
|
||||
}
|
||||
}
|
||||
if(hasFn) {
|
||||
std::get<0>(fnTuple)(std::get<1>(fnTuple));
|
||||
}
|
||||
} while(hasFn);
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Gets the number of waiting threads.
|
||||
|
||||
If all threads are waiting, this should equal ThreadCount.
|
||||
|
||||
If SIZE is less than 2, then this will always return 0.
|
||||
*/
|
||||
int getWaitCount() {
|
||||
std::lock_guard<std::mutex> lock(waitCountMutex);
|
||||
return waitCount;
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Returns true if all threads are waiting.
|
||||
|
||||
If SIZE is less than 2, then this will always return true.
|
||||
*/
|
||||
bool isAllThreadsWaiting() {
|
||||
if(SIZE >= 2) {
|
||||
std::lock_guard<std::mutex> lock(waitCountMutex);
|
||||
return waitCount == THREADCOUNT::value;
|
||||
} else {
|
||||
return true;
|
||||
sequentiallyRunTasks();
|
||||
}
|
||||
return {nullptr, nullptr, nullptr, nullptr};
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -174,18 +166,146 @@ public:
|
|||
return fnQueue.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::thread> threads;
|
||||
std::atomic_bool isAlive;
|
||||
std::condition_variable cv;
|
||||
std::mutex cvMutex;
|
||||
/*!
|
||||
\brief Returns the MAXSIZE count that this class was created with.
|
||||
*/
|
||||
constexpr unsigned int getMaxThreadCount() { return MAXSIZE; }
|
||||
|
||||
/*!
|
||||
\brief Calls startThreads() and waits until all threads have finished.
|
||||
|
||||
Regardless of the value set to MAXSIZE, this function will block until
|
||||
all previously queued functions have been executed.
|
||||
*/
|
||||
void easyStartAndWait() {
|
||||
if (MAXSIZE >= 2) {
|
||||
Internal::PointersT pointers = startThreads();
|
||||
do {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(30));
|
||||
|
||||
bool isQueueEmpty = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
isQueueEmpty = fnQueue.empty();
|
||||
}
|
||||
|
||||
if (isQueueEmpty) {
|
||||
break;
|
||||
}
|
||||
} while (true);
|
||||
if (std::get<0>(pointers)) {
|
||||
do {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(
|
||||
*std::get<1>(pointers));
|
||||
if (std::get<0>(pointers)->empty()) {
|
||||
std::get<3>(pointers)->store(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(15));
|
||||
} while (true);
|
||||
}
|
||||
} else {
|
||||
sequentiallyRunTasks();
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Checks if any threads are currently running, returning true if
|
||||
there are no threads running.
|
||||
*/
|
||||
bool isNotRunning() {
|
||||
std::lock_guard<std::mutex> lock(dequesMutex);
|
||||
auto tIter = threadStacks.begin();
|
||||
auto mIter = threadStackMutexes.begin();
|
||||
while (tIter != threadStacks.end() &&
|
||||
mIter != threadStackMutexes.end()) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(*mIter);
|
||||
if (!tIter->empty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++tIter;
|
||||
++mIter;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Internal::ThreadStacksType threadStacks;
|
||||
Internal::ThreadStacksMutexesT threadStackMutexes;
|
||||
Internal::TPQueueType fnQueue;
|
||||
std::mutex queueMutex;
|
||||
int waitCount;
|
||||
std::mutex waitCountMutex;
|
||||
Internal::ThreadCountersT threadCounters;
|
||||
Internal::PtrsHoldT ptrsHoldBools;
|
||||
std::mutex dequesMutex;
|
||||
|
||||
void sequentiallyRunTasks() {
|
||||
// pull functions from queue and run them on current thread
|
||||
Internal::TPTupleType fnTuple;
|
||||
bool hasFn;
|
||||
do {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
if (!fnQueue.empty()) {
|
||||
hasFn = true;
|
||||
fnTuple = fnQueue.front();
|
||||
fnQueue.pop();
|
||||
} else {
|
||||
hasFn = false;
|
||||
}
|
||||
}
|
||||
if (hasFn) {
|
||||
std::get<0>(fnTuple)(std::get<1>(fnTuple));
|
||||
}
|
||||
} while (hasFn);
|
||||
}
|
||||
|
||||
void checkStacks() {
|
||||
std::lock_guard<std::mutex> lock(dequesMutex);
|
||||
if (threadStacks.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool erased = false;
|
||||
do {
|
||||
erased = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(threadStackMutexes.front());
|
||||
if (ptrsHoldBools.front().load()) {
|
||||
break;
|
||||
} else if (threadStacks.front().empty()) {
|
||||
threadStacks.pop_front();
|
||||
threadCounters.pop_front();
|
||||
ptrsHoldBools.pop_front();
|
||||
erased = true;
|
||||
}
|
||||
}
|
||||
if (erased) {
|
||||
threadStackMutexes.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (!threadStacks.empty() && !threadStackMutexes.empty() &&
|
||||
!threadCounters.empty() && !ptrsHoldBools.empty());
|
||||
}
|
||||
|
||||
Internal::PointersT newStackEntry() {
|
||||
std::lock_guard<std::mutex> lock(dequesMutex);
|
||||
threadStacks.emplace_back();
|
||||
threadStackMutexes.emplace_back();
|
||||
threadCounters.emplace_back();
|
||||
threadCounters.back().store(0);
|
||||
ptrsHoldBools.emplace_back();
|
||||
ptrsHoldBools.back().store(true);
|
||||
|
||||
return {&threadStacks.back(), &threadStackMutexes.back(),
|
||||
&threadCounters.back(), &ptrsHoldBools.back()};
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace EC
|
||||
} // namespace EC
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
@ -49,6 +51,8 @@ struct Base
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual ~Base() {}
|
||||
};
|
||||
|
||||
struct Derived : public Base
|
||||
|
@ -1397,3 +1401,65 @@ TEST(EC, ManagerWithLowThreadCount) {
|
|||
EXPECT_EQ(component->y, 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EC, ManagerDeferredDeletions) {
|
||||
using ManagerType = EC::Manager<ListComponentsAll, ListTagsAll, 8>;
|
||||
ManagerType manager;
|
||||
|
||||
std::array<std::size_t, 24> entities;
|
||||
for(std::size_t i = 0; i < entities.size(); ++i) {
|
||||
entities.at(i) = manager.addEntity();
|
||||
manager.addTag<T0>(entities.at(i));
|
||||
if(i < entities.size() / 2) {
|
||||
manager.addTag<T1>(entities.at(i));
|
||||
}
|
||||
}
|
||||
|
||||
auto dataTuple = std::tuple<decltype(manager)*, std::size_t>
|
||||
{&manager, entities.size()};
|
||||
|
||||
manager.forMatchingSignature<EC::Meta::TypeList<T0, T1>>([] (std::size_t id, void *data) {
|
||||
auto *tuple = (std::tuple<ManagerType*, std::size_t>*)data;
|
||||
std::size_t size = std::get<1>(*tuple);
|
||||
std::get<0>(*tuple)->deleteEntity(id + size / 4);
|
||||
}, &dataTuple, true);
|
||||
|
||||
for(std::size_t i = 0; i < entities.size(); ++i) {
|
||||
if (entities.at(i) >= entities.size() / 4
|
||||
&& entities.at(i) < entities.size() * 3 / 4) {
|
||||
EXPECT_FALSE(manager.isAlive(entities.at(i)));
|
||||
} else {
|
||||
EXPECT_TRUE(manager.isAlive(entities.at(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(EC, NestedThreadPoolTasks) {
|
||||
using ManagerType = EC::Manager<ListComponentsAll, ListTagsAll, 2>;
|
||||
ManagerType manager;
|
||||
|
||||
std::array<std::size_t, 64> entities;
|
||||
for (auto &entity : entities) {
|
||||
entity = manager.addEntity();
|
||||
manager.addComponent<C0>(entity, entity, entity);
|
||||
}
|
||||
|
||||
manager.forMatchingSignature<EC::Meta::TypeList<C0>>([] (std::size_t id, void *data, C0 *c) {
|
||||
ManagerType *manager = (ManagerType*)data;
|
||||
|
||||
manager->forMatchingSignature<EC::Meta::TypeList<C0>>([id] (std::size_t inner_id, void* data, C0 *inner_c) {
|
||||
const C0 *const outer_c = (C0*)data;
|
||||
EXPECT_EQ(id, outer_c->x);
|
||||
EXPECT_EQ(inner_id, inner_c->x);
|
||||
if (id == inner_id) {
|
||||
EXPECT_EQ(outer_c->x, inner_c->x);
|
||||
EXPECT_EQ(outer_c->y, inner_c->y);
|
||||
} else {
|
||||
EXPECT_NE(outer_c->x, inner_c->x);
|
||||
EXPECT_NE(outer_c->y, inner_c->y);
|
||||
}
|
||||
}, c, true);
|
||||
}, &manager, true);
|
||||
|
||||
//std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
using OneThreadPool = EC::ThreadPool<1>;
|
||||
using ThreeThreadPool = EC::ThreadPool<3>;
|
||||
|
||||
TEST(ECThreadPool, CannotCompile) {
|
||||
TEST(ECThreadPool, OneThread) {
|
||||
OneThreadPool p;
|
||||
std::atomic_int data;
|
||||
data.store(0);
|
||||
|
@ -16,22 +16,22 @@ TEST(ECThreadPool, CannotCompile) {
|
|||
|
||||
p.queueFn(fn, &data);
|
||||
|
||||
p.wakeThreads();
|
||||
p.startThreads();
|
||||
|
||||
do {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while(!p.isQueueEmpty() && !p.isAllThreadsWaiting());
|
||||
} while(!p.isQueueEmpty() || !p.isNotRunning());
|
||||
|
||||
ASSERT_EQ(data.load(), 1);
|
||||
|
||||
for(unsigned int i = 0; i < 10; ++i) {
|
||||
p.queueFn(fn, &data);
|
||||
}
|
||||
p.wakeThreads();
|
||||
p.startThreads();
|
||||
|
||||
do {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while(!p.isQueueEmpty() && !p.isAllThreadsWaiting());
|
||||
} while(!p.isQueueEmpty() || !p.isNotRunning());
|
||||
|
||||
ASSERT_EQ(data.load(), 11);
|
||||
}
|
||||
|
@ -47,22 +47,60 @@ TEST(ECThreadPool, Simple) {
|
|||
|
||||
p.queueFn(fn, &data);
|
||||
|
||||
p.wakeThreads();
|
||||
p.startThreads();
|
||||
|
||||
do {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while(!p.isQueueEmpty() && !p.isAllThreadsWaiting());
|
||||
} while(!p.isQueueEmpty() || !p.isNotRunning());
|
||||
|
||||
ASSERT_EQ(data.load(), 1);
|
||||
|
||||
for(unsigned int i = 0; i < 10; ++i) {
|
||||
p.queueFn(fn, &data);
|
||||
}
|
||||
p.wakeThreads();
|
||||
p.startThreads();
|
||||
|
||||
do {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
} while(!p.isQueueEmpty() && !p.isAllThreadsWaiting());
|
||||
} while(!p.isQueueEmpty() || !p.isNotRunning());
|
||||
|
||||
ASSERT_EQ(data.load(), 11);
|
||||
}
|
||||
|
||||
TEST(ECThreadPool, QueryCount) {
|
||||
{
|
||||
OneThreadPool oneP;
|
||||
ASSERT_EQ(1, oneP.getMaxThreadCount());
|
||||
}
|
||||
{
|
||||
ThreeThreadPool threeP;
|
||||
ASSERT_EQ(3, threeP.getMaxThreadCount());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ECThreadPool, easyStartAndWait) {
|
||||
std::atomic_int data;
|
||||
data.store(0);
|
||||
{
|
||||
OneThreadPool oneP;
|
||||
for(unsigned int i = 0; i < 20; ++i) {
|
||||
oneP.queueFn([] (void *ud) {
|
||||
auto *atomicInt = static_cast<std::atomic_int*>(ud);
|
||||
atomicInt->fetch_add(1);
|
||||
}, &data);
|
||||
}
|
||||
oneP.easyStartAndWait();
|
||||
EXPECT_EQ(20, data.load());
|
||||
}
|
||||
{
|
||||
ThreeThreadPool threeP;
|
||||
for(unsigned int i = 0; i < 20; ++i) {
|
||||
threeP.queueFn([] (void *ud) {
|
||||
auto *atomicInt = static_cast<std::atomic_int*>(ud);
|
||||
atomicInt->fetch_add(1);
|
||||
}, &data);
|
||||
}
|
||||
threeP.easyStartAndWait();
|
||||
EXPECT_EQ(40, data.load());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue