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 {
150 static constexpr uint32_t IdMask = JobHandle::IdMask;
152 uint32_t m_id = IdMask;
155 ParallelCallbackHandle() =
default;
159 ParallelCallbackHandle(uint32_t
id, uint32_t gen): m_id(id), m_gen(gen) {}
162 GAIA_NODISCARD uint32_t id()
const {
167 GAIA_NODISCARD uint32_t gen()
const {
172 GAIA_NODISCARD
bool operator==(
const ParallelCallbackHandle& other)
const {
173 return m_id == other.m_id && m_gen == other.m_gen;
178 struct ParallelCallbackAllocCtx {
179 JobArgsFunc callback;
184 struct ParallelCallbackRecord: cnt::ilist_item {
185 JobArgsFunc callback;
186 std::atomic_uint32_t refs = 0;
188 ParallelCallbackRecord() =
default;
189 ~ParallelCallbackRecord() =
default;
191 ParallelCallbackRecord(
const ParallelCallbackRecord&) =
delete;
192 ParallelCallbackRecord& operator=(
const ParallelCallbackRecord&) =
delete;
194 ParallelCallbackRecord(ParallelCallbackRecord&& other)
noexcept:
195 cnt::ilist_item(GAIA_MOV(other)), callback(GAIA_MOV(other.callback)) {
196 refs.store(other.refs.load(std::memory_order_relaxed), std::memory_order_relaxed);
199 ParallelCallbackRecord& operator=(ParallelCallbackRecord&& other)
noexcept {
200 GAIA_ASSERT(core::addressof(other) !=
this);
201 cnt::ilist_item::operator=(GAIA_MOV(other));
202 callback = GAIA_MOV(other.callback);
203 refs.store(other.refs.load(std::memory_order_relaxed), std::memory_order_relaxed);
212 GAIA_NODISCARD
static ParallelCallbackRecord create(uint32_t index, uint32_t generation,
void* pCtx) {
213 auto* ctx = (ParallelCallbackAllocCtx*)pCtx;
215 ParallelCallbackRecord record{};
217 record.data.gen = generation;
218 record.callback = GAIA_MOV(ctx->callback);
219 record.refs.store(ctx->refs, std::memory_order_relaxed);
226 GAIA_NODISCARD
static ParallelCallbackHandle handle(
const ParallelCallbackRecord& record) {
227 return ParallelCallbackHandle(record.idx, record.data.gen);
233 using JobDataLayout = cnt::paged_ilist<JobContainer, JobHandle>;
234 static constexpr uint32_t JobDataPageCount = JobDataLayout::page_count_for_capacity(JobHandle::IdMask);
235 using ParallelCallbackLayout = cnt::paged_ilist<ParallelCallbackRecord, ParallelCallbackHandle>;
236 static constexpr uint32_t ParallelCallbackPageCount =
237 ParallelCallbackLayout::page_count_for_capacity(ParallelCallbackHandle::IdMask);
241 cnt::paged_ilist<JobContainer, JobHandle, JobDataPageCount> m_jobData;
244 cnt::paged_ilist<ParallelCallbackRecord, ParallelCallbackHandle, ParallelCallbackPageCount> m_parallelCallbacks;
250 GAIA_NODISCARD
bool valid(JobHandle jobHandle)
const {
251 return m_jobData.has(jobHandle);
256 JobContainer& data(JobHandle jobHandle) {
257 return m_jobData.payload_unsafe(jobHandle.id());
261 const JobContainer& data(JobHandle jobHandle)
const {
262 return m_jobData.payload_unsafe(jobHandle.id());
268 GAIA_NODISCARD JobHandle alloc_job(Job job) {
269 JobAllocCtx ctx{job.priority};
271 auto handle = m_jobData.alloc(&ctx);
272 auto& j = m_jobData[handle.id()];
275 GAIA_ASSERT(j.state == 0 || j.state == JobState::Released);
278 j.prio = ctx.priority;
280 j.func = GAIA_MOV(job.func);
289 GAIA_NODISCARD ParallelCallbackHandle alloc_parallel_callback(JobArgsFunc callback, uint32_t refs) {
290 ParallelCallbackAllocCtx ctx{};
291 ctx.callback = GAIA_MOV(callback);
293 return m_parallelCallbacks.alloc(&ctx);
302 GAIA_NODISCARD JobFunc free_job(JobHandle jobHandle) {
303 auto& jobData = m_jobData.payload_unsafe(jobHandle.id());
304 GAIA_ASSERT(is_clear(jobData) || done(jobData));
305 JobFunc func = GAIA_MOV(jobData.func);
307 jobData.state.store(JobState::Released, std::memory_order_release);
308 jobData.data.gen = (jobHandle.gen() + 1) & JobHandle::GenMask;
309 m_jobData.free_keep_live(jobHandle);
317 GAIA_NODISCARD JobArgsFunc free_parallel_callback(ParallelCallbackHandle handle) {
318 auto& record = m_parallelCallbacks[handle.id()];
319 JobArgsFunc callback = GAIA_MOV(record.callback);
320 record.refs.store(0, std::memory_order_relaxed);
321 m_parallelCallbacks.free(handle);
328 m_parallelCallbacks.clear();
333 static void run(JobContainer& jobData) {
334 if (jobData.func.operator
bool())
341 static bool signal_edge(JobContainer& jobData) {
343 const auto state = jobData.state.fetch_sub(1, std::memory_order_acq_rel) - 1;
346 const auto s = state & JobState::STATE_BITS_MASK;
347 if (s != JobState::Submitted)
351 const auto deps = state & JobState::DEP_BITS_MASK;
357 static void free_edges(JobContainer& jobData) {
359 if (jobData.edges.depCnt <= 1) {
364 mem::AllocHelper::free(jobData.edges.pDeps);
373 void dep(JobHandle jobFirst, JobHandle jobSecond) {
374 dep(std::span(&jobFirst, 1), jobSecond);
382 void dep(std::span<JobHandle> jobsFirst, JobHandle jobSecond) {
383 GAIA_ASSERT(!jobsFirst.empty());
385 GAIA_PROF_SCOPE(JobManager::dep);
387 auto& secondData = data(jobSecond);
389#if GAIA_ASSERT_ENABLED
390 GAIA_ASSERT(!busy(
const_cast<const JobContainer&
>(secondData)));
391 for (
auto jobFirst: jobsFirst) {
392 const auto& firstData = data(jobFirst);
393 GAIA_ASSERT(!busy(firstData));
397 for (
auto jobFirst: jobsFirst)
398 dep_internal(jobFirst, jobSecond);
402 const uint32_t cnt = (uint32_t)jobsFirst.size();
403 [[maybe_unused]]
const uint32_t statePrev = secondData.state.fetch_add(cnt);
404 GAIA_ASSERT((statePrev & JobState::DEP_BITS_MASK) < DEP_BITS_MASK - 1);
413 void dep_refresh(std::span<JobHandle> jobsFirst, JobHandle jobSecond) {
414 GAIA_ASSERT(!jobsFirst.empty());
416 GAIA_PROF_SCOPE(JobManager::dep_refresh);
418 auto& secondData = data(jobSecond);
420#if GAIA_ASSERT_ENABLED
421 GAIA_ASSERT(!busy(
const_cast<const JobContainer&
>(secondData)));
422 for (
auto jobFirst: jobsFirst) {
423 const auto& firstData = data(jobFirst);
424 GAIA_ASSERT(!busy(firstData));
427 for (
auto jobFirst: jobsFirst)
428 dep_refresh_internal(jobFirst, jobSecond);
433 const uint32_t cnt = (uint32_t)jobsFirst.size();
434 [[maybe_unused]]
const uint32_t statePrev = secondData.state.fetch_add(cnt);
435 GAIA_ASSERT((statePrev & JobState::DEP_BITS_MASK) < DEP_BITS_MASK - 1);
441 static uint32_t submit(JobContainer& jobData) {
442 [[maybe_unused]]
const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
443 GAIA_ASSERT(state < JobState::Submitted);
445 jobData.state.fetch_add(JobState::Submitted, std::memory_order_release) + (uint32_t)JobState::Submitted;
446#if GAIA_LOG_JOB_STATES
447 GAIA_LOG_N(
"JobHandle %u.%u - SUBMITTED", jobData.idx, jobData.gen);
454 static void processing(JobContainer& jobData) {
455 GAIA_ASSERT(submitted(
const_cast<const JobContainer&
>(jobData)));
456 jobData.state.store(JobState::Processing, std::memory_order_release);
457#if GAIA_LOG_JOB_STATES
458 GAIA_LOG_N(
"JobHandle %u.%u - PROCESSING", jobData.idx, jobData.gen);
470 static void executing(JobContainer& jobData, uint32_t workerIdx) {
471 GAIA_ASSERT(processing(
const_cast<const JobContainer&
>(jobData)));
472 jobData.state.store(JobState::Executing | workerIdx, std::memory_order_seq_cst);
473#if GAIA_LOG_JOB_STATES
474 GAIA_LOG_N(
"JobHandle %u.%u - EXECUTING", jobData.idx, jobData.gen);
480 static void finalize(JobContainer& jobData) {
481 jobData.state.store(JobState::Done, std::memory_order_release);
482#if GAIA_LOG_JOB_STATES
483 GAIA_LOG_N(
"JobHandle %u.%u - DONE", jobData.idx, jobData.gen);
489 static void reset_state(JobContainer& jobData) {
490 [[maybe_unused]]
const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
492 GAIA_ASSERT(state == 0 || state == JobState::Done);
493 jobData.state.store(0);
494#if GAIA_LOG_JOB_STATES
495 GAIA_LOG_N(
"JobHandle %u.%u - RESET_STATE", jobData.idx, jobData.gen);
502 GAIA_NODISCARD
bool is_clear(JobHandle jobHandle)
const {
503 const auto& jobData = data(jobHandle);
504 const auto state = jobData.state.load();
511 GAIA_NODISCARD
static bool is_clear(JobContainer& jobData) {
512 const auto state = jobData.state.load();
519 GAIA_NODISCARD
static bool submitted(
const JobContainer& jobData) {
520 const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
521 return state == JobState::Submitted;
527 GAIA_NODISCARD
static bool processing(
const JobContainer& jobData) {
528 const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
529 return state == JobState::Processing;
535 GAIA_NODISCARD
static bool busy(
const JobContainer& jobData) {
536 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
537 return state == JobState::Executing || state == JobState::Processing;
543 GAIA_NODISCARD
static bool done(
const JobContainer& jobData) {
544 const auto state = jobData.state.load(std::memory_order_acquire) & JobState::STATE_BITS_MASK;
545 return state == JobState::Done;
551 void invoke_parallel_callback(ParallelCallbackHandle handle,
const JobArgs& args) {
552 auto& record = m_parallelCallbacks[handle.id()];
553 GAIA_ASSERT(record.data.gen == handle.gen());
554 record.callback(args);
560 GAIA_NODISCARD
bool release_parallel_callback_ref(ParallelCallbackHandle handle) {
561 auto& record = m_parallelCallbacks[handle.id()];
562 GAIA_ASSERT(record.data.gen == handle.gen());
563 return record.refs.fetch_sub(1, std::memory_order_acq_rel) == 1;
567 void dep_internal(JobHandle jobFirst, JobHandle jobSecond) {
568 GAIA_ASSERT(jobFirst != (JobHandle)JobNull_t{});
569 GAIA_ASSERT(jobSecond != (JobHandle)JobNull_t{});
571 auto& firstData = data(jobFirst);
572 const auto depCnt0 = firstData.edges.depCnt;
573 const auto depCnt1 = ++firstData.edges.depCnt;
575#if GAIA_LOG_JOB_STATES
577 "DEP %u.%u, %u -> %u.%u", jobFirst.id(), jobFirst.gen(), firstData.edges.depCnt, jobSecond.id(),
582 firstData.edges.dep = jobSecond;
583 }
else if (depCnt1 == 2) {
584 auto prev = firstData.edges.dep;
586 firstData.edges.pDeps = mem::AllocHelper::alloc<JobHandle>(depCnt1);
587 firstData.edges.pDeps[0] = prev;
588 firstData.edges.pDeps[1] = jobSecond;
591 const bool isPow2 = core::is_pow2(depCnt0);
593 const auto nextPow2 = depCnt0 << 1;
594 auto* pPrev = firstData.edges.pDeps;
596 firstData.edges.pDeps = mem::AllocHelper::alloc<JobHandle>(nextPow2);
597 if (pPrev !=
nullptr) {
598 GAIA_FOR(depCnt0) firstData.edges.pDeps[i] = pPrev[i];
599 mem::AllocHelper::free(pPrev);
604 firstData.edges.pDeps[depCnt0] = jobSecond;
608#if GAIA_ASSERT_ENABLED
609 void dep_refresh_internal(JobHandle jobFirst, JobHandle jobSecond)
const {
610 GAIA_ASSERT(jobFirst != (JobHandle)JobNull_t{});
611 GAIA_ASSERT(jobSecond != (JobHandle)JobNull_t{});
613 const auto& firstData = data(jobFirst);
614 const auto depCnt = firstData.edges.depCnt;
617 GAIA_ASSERT(firstData.edges.dep == jobSecond);
619 GAIA_ASSERT(firstData.edges.pDeps !=
nullptr);
621 GAIA_FOR(firstData.edges.depCnt) {
622 if (firstData.edges.pDeps[i] == jobSecond) {
634 void signal_edge(JobContainer& jobData) {
635 JobManager::signal_edge(jobData);