3#include "gaia/config/config.h"
4#include "gaia/config/profiler.h"
9#include "gaia/cnt/ilist.h"
10#include "gaia/core/span.h"
11#include "gaia/core/utility.h"
12#include "gaia/mem/mem_alloc.h"
13#include "gaia/mt/jobcommon.h"
14#include "gaia/mt/jobhandle.h"
16#define GAIA_LOG_JOB_STATES 0
22 enum JobState : uint32_t {
25 DEP_BITS_MASK = (uint32_t)((1u << DEP_BITS) - 1),
27 STATE_BITS_START = DEP_BITS_START + DEP_BITS,
29 STATE_BITS_MASK = (uint32_t)(((1u << STATE_BITS) - 1) << STATE_BITS_START),
34 Submitted = 0x01 << STATE_BITS_START,
36 Processing = 0x02 << STATE_BITS_START,
38 Executing = 0x03 << STATE_BITS_START,
40 Done = 0x04 << STATE_BITS_START,
42 Released = 0x05 << STATE_BITS_START
48 inline void signal_edge(JobContainer& jobData);
69 struct JobContainer: cnt::ilist_item {
80 std::atomic_uint32_t state;
84 JobCreationFlags flags;
90 JobContainer() =
default;
91 ~JobContainer() =
default;
93 JobContainer(
const JobContainer& other) =
delete;
94 JobContainer& operator=(
const JobContainer& other) =
delete;
96 JobContainer(JobContainer&& other): cnt::ilist_item(GAIA_MOV(other)) {
97 state = other.state.load();
100 func = GAIA_MOV(other.func);
105 other.edges.depCnt = 0;
107 JobContainer& operator=(JobContainer&& other) {
108 GAIA_ASSERT(core::addressof(other) !=
this);
109 cnt::ilist_item::operator=(GAIA_MOV(other));
110 state = other.state.load();
113 func = GAIA_MOV(other.func);
118 other.edges.depCnt = 0;
128 GAIA_NODISCARD
static JobContainer create(uint32_t index, uint32_t generation,
void* pCtx) {
129 auto* ctx = (JobAllocCtx*)pCtx;
133 jc.data.gen = generation;
134 jc.prio = ctx->priority;
142 GAIA_NODISCARD
static JobHandle handle(
const JobContainer& jc) {
143 return JobHandle(jc.idx, jc.data.gen, (jc.prio == JobPriority::Low) != 0);
148 struct ParallelCallbackHandle {
149 static constexpr uint32_t IdMask = uint32_t(-1);
151 uint32_t m_id = IdMask;
154 ParallelCallbackHandle() =
default;
158 ParallelCallbackHandle(uint32_t
id, uint32_t gen): m_id(id), m_gen(gen) {}
161 GAIA_NODISCARD uint32_t id()
const {
166 GAIA_NODISCARD uint32_t gen()
const {
171 GAIA_NODISCARD
bool operator==(
const ParallelCallbackHandle& other)
const {
172 return m_id == other.m_id && m_gen == other.m_gen;
177 struct ParallelCallbackAllocCtx {
178 JobArgsFunc callback;
183 struct ParallelCallbackRecord: cnt::ilist_item {
184 JobArgsFunc callback;
185 std::atomic_uint32_t refs = 0;
187 ParallelCallbackRecord() =
default;
188 ~ParallelCallbackRecord() =
default;
190 ParallelCallbackRecord(
const ParallelCallbackRecord&) =
delete;
191 ParallelCallbackRecord& operator=(
const ParallelCallbackRecord&) =
delete;
193 ParallelCallbackRecord(ParallelCallbackRecord&& other)
noexcept:
194 cnt::ilist_item(GAIA_MOV(other)), callback(GAIA_MOV(other.callback)) {
195 refs.store(other.refs.load(std::memory_order_relaxed), std::memory_order_relaxed);
198 ParallelCallbackRecord& operator=(ParallelCallbackRecord&& other)
noexcept {
199 GAIA_ASSERT(core::addressof(other) !=
this);
200 cnt::ilist_item::operator=(GAIA_MOV(other));
201 callback = GAIA_MOV(other.callback);
202 refs.store(other.refs.load(std::memory_order_relaxed), std::memory_order_relaxed);
211 GAIA_NODISCARD
static ParallelCallbackRecord create(uint32_t index, uint32_t generation,
void* pCtx) {
212 auto* ctx = (ParallelCallbackAllocCtx*)pCtx;
214 ParallelCallbackRecord record{};
216 record.data.gen = generation;
217 record.callback = GAIA_MOV(ctx->callback);
218 record.refs.store(ctx->refs, std::memory_order_relaxed);
225 GAIA_NODISCARD
static ParallelCallbackHandle handle(
const ParallelCallbackRecord& record) {
226 return ParallelCallbackHandle(record.idx, record.data.gen);
232 using JobDataLayout = cnt::paged_ilist<JobContainer, JobHandle>;
233 static constexpr uint32_t JobDataPageCount = JobDataLayout::page_count_for_capacity(JobHandle::IdMask);
237 cnt::paged_ilist<JobContainer, JobHandle, JobDataPageCount> m_jobData;
239 cnt::ilist<ParallelCallbackRecord, ParallelCallbackHandle> m_parallelCallbacks;
245 GAIA_NODISCARD
bool valid(JobHandle jobHandle)
const {
246 return m_jobData.has(jobHandle);
251 JobContainer& data(JobHandle jobHandle) {
252 return m_jobData.payload_unsafe(jobHandle.id());
256 const JobContainer& data(JobHandle jobHandle)
const {
257 return m_jobData.payload_unsafe(jobHandle.id());
263 GAIA_NODISCARD JobHandle alloc_job(Job job) {
264 JobAllocCtx ctx{job.priority};
266 auto handle = m_jobData.alloc(&ctx);
267 auto& j = m_jobData[handle.id()];
270 GAIA_ASSERT(j.state == 0 || j.state == JobState::Released);
273 j.prio = ctx.priority;
275 j.func = GAIA_MOV(job.func);
284 GAIA_NODISCARD ParallelCallbackHandle alloc_parallel_callback(JobArgsFunc callback, uint32_t refs) {
285 ParallelCallbackAllocCtx ctx{};
286 ctx.callback = GAIA_MOV(callback);
288 return m_parallelCallbacks.alloc(&ctx);
295 void free_job(JobHandle jobHandle) {
296 auto& jobData = m_jobData.payload_unsafe(jobHandle.id());
297 GAIA_ASSERT(is_clear(jobData) || done(jobData));
299 jobData.state.store(JobState::Released, std::memory_order_release);
300 jobData.data.gen = (jobHandle.gen() + 1) & JobHandle::GenMask;
301 m_jobData.free_keep_live(jobHandle);
306 void free_parallel_callback(ParallelCallbackHandle handle) {
307 auto& record = m_parallelCallbacks[handle.id()];
308 record.callback.reset();
309 record.refs.store(0, std::memory_order_relaxed);
310 m_parallelCallbacks.free(handle);
316 m_parallelCallbacks.clear();
321 static void run(JobContainer& jobData) {
322 if (jobData.func.operator
bool())
329 static bool signal_edge(JobContainer& jobData) {
331 const auto state = jobData.state.fetch_sub(1, std::memory_order_acq_rel) - 1;
334 const auto s = state & JobState::STATE_BITS_MASK;
335 if (s != JobState::Submitted)
339 const auto deps = state & JobState::DEP_BITS_MASK;
345 static void free_edges(JobContainer& jobData) {
347 if (jobData.edges.depCnt <= 1) {
352 mem::AllocHelper::free(jobData.edges.pDeps);
361 void dep(JobHandle jobFirst, JobHandle jobSecond) {
362 dep(std::span(&jobFirst, 1), jobSecond);
370 void dep(std::span<JobHandle> jobsFirst, JobHandle jobSecond) {
371 GAIA_ASSERT(!jobsFirst.empty());
373 GAIA_PROF_SCOPE(JobManager::dep);
375 auto& secondData = data(jobSecond);
377#if GAIA_ASSERT_ENABLED
378 GAIA_ASSERT(!busy(
const_cast<const JobContainer&
>(secondData)));
379 for (
auto jobFirst: jobsFirst) {
380 const auto& firstData = data(jobFirst);
381 GAIA_ASSERT(!busy(firstData));
385 for (
auto jobFirst: jobsFirst)
386 dep_internal(jobFirst, jobSecond);
390 const uint32_t cnt = (uint32_t)jobsFirst.size();
391 [[maybe_unused]]
const uint32_t statePrev = secondData.state.fetch_add(cnt);
392 GAIA_ASSERT((statePrev & JobState::DEP_BITS_MASK) < DEP_BITS_MASK - 1);
401 void dep_refresh(std::span<JobHandle> jobsFirst, JobHandle jobSecond) {
402 GAIA_ASSERT(!jobsFirst.empty());
404 GAIA_PROF_SCOPE(JobManager::dep_refresh);
406 auto& secondData = data(jobSecond);
408#if GAIA_ASSERT_ENABLED
409 GAIA_ASSERT(!busy(
const_cast<const JobContainer&
>(secondData)));
410 for (
auto jobFirst: jobsFirst) {
411 const auto& firstData = data(jobFirst);
412 GAIA_ASSERT(!busy(firstData));
415 for (
auto jobFirst: jobsFirst)
416 dep_refresh_internal(jobFirst, jobSecond);
421 const uint32_t cnt = (uint32_t)jobsFirst.size();
422 [[maybe_unused]]
const uint32_t statePrev = secondData.state.fetch_add(cnt);
423 GAIA_ASSERT((statePrev & JobState::DEP_BITS_MASK) < DEP_BITS_MASK - 1);
429 static uint32_t submit(JobContainer& jobData) {
430 [[maybe_unused]]
const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
431 GAIA_ASSERT(state < JobState::Submitted);
433 jobData.state.fetch_add(JobState::Submitted, std::memory_order_release) + (uint32_t)JobState::Submitted;
434#if GAIA_LOG_JOB_STATES
435 GAIA_LOG_N(
"JobHandle %u.%u - SUBMITTED", jobData.idx, jobData.gen);
442 static void processing(JobContainer& jobData) {
443 GAIA_ASSERT(submitted(
const_cast<const JobContainer&
>(jobData)));
444 jobData.state.store(JobState::Processing, std::memory_order_release);
445#if GAIA_LOG_JOB_STATES
446 GAIA_LOG_N(
"JobHandle %u.%u - PROCESSING", jobData.idx, jobData.gen);
453 static void executing(JobContainer& jobData, uint32_t workerIdx) {
454 GAIA_ASSERT(processing(
const_cast<const JobContainer&
>(jobData)));
455 jobData.state.store(JobState::Executing | workerIdx, std::memory_order_release);
456#if GAIA_LOG_JOB_STATES
457 GAIA_LOG_N(
"JobHandle %u.%u - EXECUTING", jobData.idx, jobData.gen);
463 static void finalize(JobContainer& jobData) {
464 jobData.state.store(JobState::Done, std::memory_order_release);
465#if GAIA_LOG_JOB_STATES
466 GAIA_LOG_N(
"JobHandle %u.%u - DONE", jobData.idx, jobData.gen);
472 static void reset_state(JobContainer& jobData) {
473 [[maybe_unused]]
const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
475 GAIA_ASSERT(state == 0 || state == JobState::Done);
476 jobData.state.store(0);
477#if GAIA_LOG_JOB_STATES
478 GAIA_LOG_N(
"JobHandle %u.%u - RESET_STATE", jobData.idx, jobData.gen);
485 GAIA_NODISCARD
bool is_clear(JobHandle jobHandle)
const {
486 const auto& jobData = data(jobHandle);
487 const auto state = jobData.state.load();
494 GAIA_NODISCARD
static bool is_clear(JobContainer& jobData) {
495 const auto state = jobData.state.load();
502 GAIA_NODISCARD
static bool submitted(
const JobContainer& jobData) {
503 const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
504 return state == JobState::Submitted;
510 GAIA_NODISCARD
static bool processing(
const JobContainer& jobData) {
511 const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
512 return state == JobState::Processing;
518 GAIA_NODISCARD
static bool busy(
const JobContainer& jobData) {
519 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
520 return state == JobState::Executing || state == JobState::Processing;
526 GAIA_NODISCARD
static bool done(
const JobContainer& jobData) {
527 const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
528 return state == JobState::Done;
534 void invoke_parallel_callback(ParallelCallbackHandle handle,
const JobArgs& args) {
535 auto& record = m_parallelCallbacks[handle.id()];
536 GAIA_ASSERT(record.data.gen == handle.gen());
537 record.callback(args);
543 GAIA_NODISCARD
bool release_parallel_callback_ref(ParallelCallbackHandle handle) {
544 auto& record = m_parallelCallbacks[handle.id()];
545 GAIA_ASSERT(record.data.gen == handle.gen());
546 return record.refs.fetch_sub(1, std::memory_order_acq_rel) == 1;
550 void dep_internal(JobHandle jobFirst, JobHandle jobSecond) {
551 GAIA_ASSERT(jobFirst != (JobHandle)JobNull_t{});
552 GAIA_ASSERT(jobSecond != (JobHandle)JobNull_t{});
554 auto& firstData = data(jobFirst);
555 const auto depCnt0 = firstData.edges.depCnt;
556 const auto depCnt1 = ++firstData.edges.depCnt;
558#if GAIA_LOG_JOB_STATES
560 "DEP %u.%u, %u -> %u.%u", jobFirst.id(), jobFirst.gen(), firstData.edges.depCnt, jobSecond.id(),
565 firstData.edges.dep = jobSecond;
566 }
else if (depCnt1 == 2) {
567 auto prev = firstData.edges.dep;
569 firstData.edges.pDeps = mem::AllocHelper::alloc<JobHandle>(depCnt1);
570 firstData.edges.pDeps[0] = prev;
571 firstData.edges.pDeps[1] = jobSecond;
574 const bool isPow2 = core::is_pow2(depCnt0);
576 const auto nextPow2 = depCnt0 << 1;
577 auto* pPrev = firstData.edges.pDeps;
579 firstData.edges.pDeps = mem::AllocHelper::alloc<JobHandle>(nextPow2);
580 if (pPrev !=
nullptr) {
581 GAIA_FOR(depCnt0) firstData.edges.pDeps[i] = pPrev[i];
582 mem::AllocHelper::free(pPrev);
587 firstData.edges.pDeps[depCnt0] = jobSecond;
591#if GAIA_ASSERT_ENABLED
592 void dep_refresh_internal(JobHandle jobFirst, JobHandle jobSecond)
const {
593 GAIA_ASSERT(jobFirst != (JobHandle)JobNull_t{});
594 GAIA_ASSERT(jobSecond != (JobHandle)JobNull_t{});
596 const auto& firstData = data(jobFirst);
597 const auto depCnt = firstData.edges.depCnt;
600 GAIA_ASSERT(firstData.edges.dep == jobSecond);
602 GAIA_ASSERT(firstData.edges.pDeps !=
nullptr);
604 GAIA_FOR(firstData.edges.depCnt) {
605 if (firstData.edges.pDeps[i] == jobSecond) {
617 void signal_edge(JobContainer& jobData) {
618 JobManager::signal_edge(jobData);