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());
+ }
+}