diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000..7f08984 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -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 diff --git a/Doxyfile b/Doxyfile index 12b1307..efbdb59 100644 --- a/Doxyfile +++ b/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. diff --git a/README.md b/README.md index 08f9fbe..9d8fe13 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ 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/) + # Compiling the UnitTests Create a build directory. diff --git a/doxygen/mainpage.dox b/doxygen/mainpage.dox new file mode 100644 index 0000000..7c1419e --- /dev/null +++ b/doxygen/mainpage.dox @@ -0,0 +1,9 @@ +/*! + \mainpage EntityComponentMetaSystem Index Page + + \ref md_README + + Classes + + \ref EC::Manager "The Manager class that manages an Entity Component System" + */ diff --git a/src/EC/Manager.hpp b/src/EC/Manager.hpp index 2a949eb..6375fc6 100644 --- a/src/EC/Manager.hpp +++ b/src/EC/Manager.hpp @@ -773,11 +773,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } } @@ -894,11 +890,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } } @@ -1039,12 +1031,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for( - std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } }))); @@ -1119,11 +1106,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } return matchingV; @@ -1496,11 +1479,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } // call functions on matching entities @@ -1562,12 +1541,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for( - std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } } ); @@ -1705,11 +1679,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } // call functions on matching entities @@ -1776,12 +1746,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for( - std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } } ); @@ -1859,11 +1824,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } } @@ -1952,11 +1913,7 @@ namespace EC } }, &fnDataAr[i]); } - threadPool->wakeThreads(); - do { - std::this_thread::sleep_for(std::chrono::microseconds(200)); - } while(!threadPool->isQueueEmpty() - || !threadPool->isAllThreadsWaiting()); + threadPool->easyWakeAndWait(); } } }; diff --git a/src/EC/ThreadPool.hpp b/src/EC/ThreadPool.hpp index 3038b02..8a47cb1 100644 --- a/src/EC/ThreadPool.hpp +++ b/src/EC/ThreadPool.hpp @@ -117,24 +117,7 @@ public: cv.notify_one(); } } else { - // pull functions from queue and run them on main thread - Internal::TPTupleType fnTuple; - bool hasFn; - do { - { - std::lock_guard 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); + sequentiallyRunTasks(); } } @@ -179,6 +162,26 @@ public: return SIZE; } + /*! + \brief Wakes all threads and blocks until all queued tasks are finished. + + If SIZE is less than 2, then this function call will block until all the + queued functions have been executed on the calling thread. + + If SIZE is 2 or greater, then this function will block until all the + queued functions have been executed by the threads in the thread pool. + */ + void easyWakeAndWait() { + if(SIZE >= 2) { + wakeThreads(); + do { + std::this_thread::sleep_for(std::chrono::microseconds(150)); + } while(!isQueueEmpty() || !isAllThreadsWaiting()); + } else { + sequentiallyRunTasks(); + } + } + private: std::vector threads; std::atomic_bool isAlive; @@ -189,6 +192,27 @@ private: int waitCount; std::mutex waitCountMutex; + void sequentiallyRunTasks() { + // pull functions from queue and run them on current thread + Internal::TPTupleType fnTuple; + bool hasFn; + do { + { + std::lock_guard 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); + } + }; } // namespace EC diff --git a/src/test/ThreadPoolTest.cpp b/src/test/ThreadPoolTest.cpp index 5a1a51d..9ebcbe1 100644 --- a/src/test/ThreadPoolTest.cpp +++ b/src/test/ThreadPoolTest.cpp @@ -77,3 +77,30 @@ TEST(ECThreadPool, QueryCount) { ASSERT_EQ(3, threeP.getThreadCount()); } } + +TEST(ECThreadPool, easyWakeAndWait) { + 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(ud); + atomicInt->fetch_add(1); + }, &data); + } + oneP.easyWakeAndWait(); + EXPECT_EQ(20, data.load()); + } + { + ThreeThreadPool threeP; + for(unsigned int i = 0; i < 20; ++i) { + threeP.queueFn([] (void *ud) { + auto *atomicInt = static_cast(ud); + atomicInt->fetch_add(1); + }, &data); + } + threeP.easyWakeAndWait(); + EXPECT_EQ(40, data.load()); + } +}