feat: add JobSystem
This commit is contained in:
1 parent
7abe692adf
commit
aab3bde601
1 file changed
+160
@@ -0,0 +1,160 @@
|
|||||||
|
#include "vf/JobSystem.h"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace vf {
|
||||||
|
|
||||||
|
struct JobHandle::State {
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable cv;
|
||||||
|
std::exception_ptr error;
|
||||||
|
bool finished = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JobSystem::GraphRun {
|
||||||
|
explicit GraphRun(std::size_t count)
|
||||||
|
: work(count),
|
||||||
|
names(count),
|
||||||
|
priorities(count, JobPriority::Normal),
|
||||||
|
dependents(count),
|
||||||
|
remaining(count),
|
||||||
|
depFailed(count),
|
||||||
|
states(count) {}
|
||||||
|
|
||||||
|
std::vector<std::function<void()>> work;
|
||||||
|
std::vector<std::string> names;
|
||||||
|
std::vector<JobPriority> priorities;
|
||||||
|
std::vector<std::vector<std::size_t>> dependents;
|
||||||
|
std::vector<std::atomic<int>> remaining;
|
||||||
|
std::vector<std::atomic<bool>> depFailed;
|
||||||
|
std::vector<std::shared_ptr<JobHandle::State>> states;
|
||||||
|
bool serial = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool JobHandle::done() const {
|
||||||
|
if (!state) return true;
|
||||||
|
std::lock_guard<std::mutex> lock(state->mutex);
|
||||||
|
return state->finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JobHandle::failed() const {
|
||||||
|
if (!state) return false;
|
||||||
|
std::lock_guard<std::mutex> lock(state->mutex);
|
||||||
|
return state->finished && state->error != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobHandle::wait() const {
|
||||||
|
if (!state) return;
|
||||||
|
std::unique_lock<std::mutex> lock(state->mutex);
|
||||||
|
state->cv.wait(lock, [this] { return state->finished; });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::exception_ptr JobHandle::error() const {
|
||||||
|
if (!state) return nullptr;
|
||||||
|
std::lock_guard<std::mutex> lock(state->mutex);
|
||||||
|
return state->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string JobHandle::errorMessage() const {
|
||||||
|
std::exception_ptr captured = error();
|
||||||
|
if (!captured) return std::string();
|
||||||
|
try {
|
||||||
|
std::rethrow_exception(captured);
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
return ex.what();
|
||||||
|
} catch (...) {
|
||||||
|
return std::string("unknown exception");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobHandle::rethrowError() const {
|
||||||
|
std::exception_ptr captured = error();
|
||||||
|
if (captured) std::rethrow_exception(captured);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobBatch::wait() const {
|
||||||
|
for (const JobHandle& handle : handles) handle.wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JobBatch::done() const {
|
||||||
|
for (const JobHandle& handle : handles)
|
||||||
|
if (!handle.done()) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t JobBatch::failedCount() const {
|
||||||
|
std::size_t count = 0;
|
||||||
|
for (const JobHandle& handle : handles)
|
||||||
|
if (handle.failed()) ++count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::exception_ptr JobBatch::firstError() const {
|
||||||
|
for (const JobHandle& handle : handles) {
|
||||||
|
std::exception_ptr captured = handle.error();
|
||||||
|
if (captured) return captured;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t JobGraph::addNode(std::string name, std::function<void()> work, JobPriority priority) {
|
||||||
|
JobNode node;
|
||||||
|
node.name = std::move(name);
|
||||||
|
node.work = std::move(work);
|
||||||
|
node.priority = priority;
|
||||||
|
nodes.push_back(std::move(node));
|
||||||
|
return nodes.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JobGraph::addDependency(std::size_t node, std::size_t dependsOn) {
|
||||||
|
nodes[node].dependencies.push_back(dependsOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::size_t> JobGraph::topologicalOrder() const {
|
||||||
|
std::vector<int> indegree(nodes.size(), 0);
|
||||||
|
std::vector<std::vector<std::size_t>> dependents(nodes.size());
|
||||||
|
for (std::size_t i = 0; i < nodes.size(); ++i) {
|
||||||
|
for (std::size_t dep : nodes[i].dependencies) {
|
||||||
|
++indegree[i];
|
||||||
|
dependents[dep].push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::size_t> ready;
|
||||||
|
for (std::size_t i = 0; i < nodes.size(); ++i)
|
||||||
|
if (indegree[i] == 0) ready.push_back(i);
|
||||||
|
|
||||||
|
std::vector<std::size_t> order;
|
||||||
|
order.reserve(nodes.size());
|
||||||
|
for (std::size_t cursor = 0; cursor < ready.size(); ++cursor) {
|
||||||
|
std::size_t current = ready[cursor];
|
||||||
|
order.push_back(current);
|
||||||
|
for (std::size_t dependent : dependents[current])
|
||||||
|
if (--indegree[dependent] == 0) ready.push_back(dependent);
|
||||||
|
}
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JobGraph::hasCycle() const {
|
||||||
|
return topologicalOrder().size() != nodes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
JobSystem::JobSystem(unsigned threadCount) {
|
||||||
|
workers.reserve(threadCount);
|
||||||
|
for (unsigned i = 0; i < threadCount; ++i)
|
||||||
|
workers.emplace_back([this] { workerLoop(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
JobSystem::~JobSystem() {
|
||||||
|
if (workers.empty()) return;
|
||||||
|
waitAll();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex);
|
||||||
|
stopping = true;
|
||||||
|
}
|
||||||
|
workCv.notify_all();
|
||||||
|
for (std::thread& worker : workers) worker.join();
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user