79 friend class JobManager;
85 static constexpr uint32_t MaxWorkers = JobState::DEP_BITS;
88 std::thread::id m_mainThreadId;
91 std::atomic_bool m_stop{};
97 MpmcQueue<JobHandle, 1024> m_jobQueue[JobPriorityCnt];
99 MpmcQueue<JobHandle, 1024> m_jobQueueBackground;
101 uint32_t m_frameWorkersCnt = 0;
103 uint32_t m_backgroundWorkersCnt = 0;
105 uint32_t m_workersCnt[JobPriorityCnt]{};
110 uint32_t m_workerThreadsCnt[JobPriorityCnt]{};
117 std::atomic_uint32_t m_blockedInWorkUntil;
120 JobManager m_jobManager;
126 GAIA_PROF_MUTEX(
SpinLock, m_jobAllocMtx);
128#if GAIA_ECS_TEST_HOOKS
132 inline static void (*s_testBeforeFinalize)(
JobHandle jobHandle) =
nullptr;
138 static void test_before_finalize([[maybe_unused]]
JobHandle jobHandle) {
139#if GAIA_ECS_TEST_HOOKS
140 if (s_testBeforeFinalize !=
nullptr)
141 s_testBeforeFinalize(jobHandle);
151 const auto hwThreads = hw_thread_cnt();
152 const auto hwEffThreads = hw_efficiency_cores_cnt();
153 uint32_t hiPrioWorkers = hwThreads;
154 if (hwEffThreads < hwThreads)
155 hiPrioWorkers -= hwEffThreads;
157 set_max_workers(hwThreads, hiPrioWorkers);
179 m_mainThreadId = std::this_thread::get_id();
180 if (!m_workersCtx.empty())
181 detail::tl_workerCtx = &m_workersCtx[0];
187 return m_frameWorkersCnt;
193 return m_backgroundWorkersCnt;
203 const auto maxFrameWorkers = MaxWorkers - m_backgroundWorkersCnt;
204 const auto workersCnt = core::get_max(core::get_min(maxFrameWorkers, count), 1U);
205 countHighPrio = core::get_min(countHighPrio, workersCnt);
211 for (
auto& ctx: m_workersCtx)
214 m_frameWorkersCnt = workersCnt - 1;
218 m_workersCtx.resize(workersCnt + m_backgroundWorkersCnt);
220 m_workers.
resize(m_frameWorkersCnt + m_backgroundWorkersCnt);
225 detail::tl_workerCtx = m_workersCtx.
data();
226 m_workersCtx[0].tp =
this;
227 m_workersCtx[0].workerIdx = 0;
228 m_workersCtx[0].prio = JobPriority::High;
231 for (
auto& worker: m_workers)
235 uint32_t workerIdx = 1;
236 set_workers_high_prio_inter(workerIdx, countHighPrio);
237 create_background_worker_threads(workerIdx);
245 count = gaia::core::get_min(count, m_frameWorkersCnt);
246 m_workerThreadsCnt[0] = count;
247 m_workerThreadsCnt[1] = m_frameWorkersCnt - count;
248 m_workersCnt[0] = count + 1;
249 m_workersCnt[1] = m_workerThreadsCnt[1];
252 create_worker_threads(workerIdx, JobPriority::High, m_workerThreadsCnt[0]);
253 create_worker_threads(workerIdx, JobPriority::Low, m_workerThreadsCnt[1]);
261 const uint32_t realCnt = gaia::core::get_min(count, m_frameWorkersCnt);
262 m_workerThreadsCnt[0] = m_frameWorkersCnt - realCnt;
263 m_workerThreadsCnt[1] = realCnt;
264 m_workersCnt[0] = m_workerThreadsCnt[0] + 1;
265 m_workersCnt[1] = m_workerThreadsCnt[1];
268 create_worker_threads(workerIdx, JobPriority::High, m_workerThreadsCnt[0]);
269 create_worker_threads(workerIdx, JobPriority::Low, m_workerThreadsCnt[1]);
277 detail::tl_workerCtx = m_workersCtx.data();
279 uint32_t workerIdx = 1;
280 set_workers_high_prio_inter(workerIdx, count);
281 create_background_worker_threads(workerIdx);
289 detail::tl_workerCtx = m_workersCtx.data();
291 uint32_t workerIdx = 1;
292 set_workers_low_prio_inter(workerIdx, count);
293 create_background_worker_threads(workerIdx);
302 const auto maxBackgroundWorkers = MaxWorkers - 1;
303 count = core::get_min(maxBackgroundWorkers, count);
305 const auto frameWorkersCntOld = m_frameWorkersCnt;
306 const auto highWorkersCntOld = m_workerThreadsCnt[0];
311 m_backgroundWorkersCnt = count;
313 const auto maxFrameWorkers = MaxWorkers - m_backgroundWorkersCnt - 1;
314 m_frameWorkersCnt = core::get_min(frameWorkersCntOld, maxFrameWorkers);
316 for (
auto& ctx: m_workersCtx)
319 m_workersCtx.resize(m_frameWorkersCnt + 1 + m_backgroundWorkersCnt);
320 m_workers.
resize(m_frameWorkersCnt + m_backgroundWorkersCnt);
322 detail::tl_workerCtx = m_workersCtx.
data();
323 m_workersCtx[0].tp =
this;
324 m_workersCtx[0].workerIdx = 0;
325 m_workersCtx[0].prio = JobPriority::High;
327 for (
auto& worker: m_workers)
330 uint32_t workerIdx = 1;
331 set_workers_high_prio_inter(workerIdx, highWorkersCntOld);
332 create_background_worker_threads(workerIdx);
341 GAIA_ASSERT(main_thread());
343 m_jobManager.dep(std::span(&jobFirst, 1), jobSecond);
352 GAIA_ASSERT(main_thread());
354 m_jobManager.dep(jobsFirst, jobSecond);
365 GAIA_ASSERT(main_thread());
367 m_jobManager.dep_refresh(std::span(&jobFirst, 1), jobSecond);
378 GAIA_ASSERT(main_thread());
380 m_jobManager.dep_refresh(jobsFirst, jobSecond);
390 template <
typename TJob>
392 GAIA_ASSERT(main_thread());
394 job.priority = final_prio(job);
396 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_jobAllocMtx);
398 GAIA_PROF_LOCK_MARK(m_jobAllocMtx);
400 return m_jobManager.alloc_job(GAIA_MOV(job));
404 void add_n(JobPriority prio, std::span<JobHandle> jobHandles) {
405 GAIA_ASSERT(main_thread());
406 GAIA_ASSERT(!jobHandles.empty());
408 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_jobAllocMtx);
410 GAIA_PROF_LOCK_MARK(m_jobAllocMtx);
412 for (
auto& jobHandle: jobHandles)
413 jobHandle = m_jobManager.alloc_job({{}, prio, JobCreationFlags::Default});
416 GAIA_NODISCARD ParallelCallbackHandle add_parallel_callback(JobArgsFunc callback, uint32_t refs) {
417 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_jobAllocMtx);
418 core::lock_scope lock(mtx);
419 GAIA_PROF_LOCK_MARK(m_jobAllocMtx);
421 return m_jobManager.alloc_parallel_callback(GAIA_MOV(callback), refs);
424 void release_parallel_callback(ParallelCallbackHandle handle) {
425 if (!m_jobManager.release_parallel_callback_ref(handle))
429 JobArgsFunc callback;
431 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_jobAllocMtx);
432 core::lock_scope lock(mtx);
433 GAIA_PROF_LOCK_MARK(m_jobAllocMtx);
435 callback = m_jobManager.free_parallel_callback(handle);
439 void release_job(JobHandle jobHandle) {
444 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_jobAllocMtx);
445 core::lock_scope lock(mtx);
446 GAIA_PROF_LOCK_MARK(m_jobAllocMtx);
448 func = m_jobManager.free_job(jobHandle);
458 GAIA_NODISCARD JobFunc
459 parallel_group_func(ParallelCallbackHandle callbackHandle, uint32_t idxStart, uint32_t idxEnd) {
460 auto func = [
this, callbackHandle, idxStart, idxEnd]() {
462 args.idxStart = idxStart;
463 args.idxEnd = idxEnd;
464 m_jobManager.invoke_parallel_callback(callbackHandle, args);
465 release_parallel_callback(callbackHandle);
467 static_assert(JobFunc::stores_inline<decltype(func)>(),
"Parallel group jobs must not allocate");
468 return JobFunc(GAIA_MOV(func));
476 GAIA_NODISCARD
static JobFunc parallel_group_func(
const JobParallelRef& job, uint32_t idxStart, uint32_t idxEnd) {
477 auto func = [pCtx = job.pCtx, invoke = job.invoke, idxStart, idxEnd]() {
479 args.idxStart = idxStart;
480 args.idxEnd = idxEnd;
483 static_assert(JobFunc::stores_inline<decltype(func)>(),
"Parallel group jobs must not allocate");
484 return JobFunc(GAIA_MOV(func));
499 auto& mtx = GAIA_PROF_EXTRACT_MUTEX(m_jobAllocMtx);
501 GAIA_PROF_LOCK_MARK(m_jobAllocMtx);
502 if (!m_jobManager.valid(jobHandle))
505#if GAIA_ASSERT_ENABLED
507 const auto& jobData = m_jobManager.data(jobHandle);
508 GAIA_ASSERT(jobData.state == 0 || m_jobManager.done(jobData));
512 func = m_jobManager.free_job(jobHandle);
522 void submit(std::span<JobHandle> jobHandles) {
523 if (jobHandles.empty())
526 GAIA_PROF_SCOPE(tp::submitn);
531 for (
auto handle: jobHandles) {
534 auto& jobData = m_jobManager.data(handle);
535 if GAIA_UNLIKELY (jobData.data.gen != handle.gen())
538 const auto state = m_jobManager.submit(jobData) & JobState::DEP_BITS_MASK;
544 pHandles[cnt++] = handle;
547 auto* ctx = detail::tl_workerCtx;
548 process(std::span(pHandles, cnt), ctx);
559 GAIA_PROF_SCOPE(tp::submit);
561 auto& jobData = m_jobManager.data(jobHandle);
562 if GAIA_UNLIKELY (jobData.data.gen != jobHandle.
gen())
565 const auto state = m_jobManager.submit(jobData) & JobState::DEP_BITS_MASK;
569 auto* ctx = detail::tl_workerCtx;
570 process(std::span(&jobHandle, 1), ctx);
576 if (jobHandles.empty())
579 GAIA_PROF_SCOPE(tp::reset);
581 for (
auto handle: jobHandles) {
582 auto& jobData = m_jobManager.data(handle);
583 m_jobManager.reset_state(jobData);
590 reset_state(std::span(&jobHandle, 1));
596 void reset(std::span<JobHandle> jobHandles) {
597 if (jobHandles.empty())
600 GAIA_ASSERT(main_thread());
601 GAIA_PROF_SCOPE(tp::reset_wait);
604 for (
auto handle: jobHandles) {
610 for (
auto handle: jobHandles) {
614 auto& jobData = m_jobManager.data(handle);
615 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
617 if (state == JobState::Released)
620 m_jobManager.reset_state(jobData);
627 reset(std::span(&jobHandle, 1));
636 JobHandle jobHandle = add(GAIA_MOV(job));
650 job.
flags = (JobCreationFlags)((uint8_t)job.
flags | (uint8_t)JobCreationFlags::Background);
651 JobHandle jobHandle = add(GAIA_MOV(job));
663 JobHandle jobHandle = add(GAIA_MOV(job));
664 dep(dependsOn, jobHandle);
677 GAIA_ASSERT(main_thread());
680 GAIA_ASSERT(itemsToProcess != 0);
681 if (itemsToProcess == 0)
685 if GAIA_UNLIKELY (m_stop)
689 const auto prio = job.
priority = final_prio(job);
692 if (groupSize == 0) {
693 const auto cntWorkers = core::get_max(1U, m_workersCnt[(uint32_t)prio]);
694 groupSize = itemsToProcess / cntWorkers + (itemsToProcess % cntWorkers != 0);
702 constexpr uint32_t maxUnitsOfWorkPerGroup = 8;
703 groupSize = groupSize / maxUnitsOfWorkPerGroup;
708 const auto jobs = itemsToProcess / groupSize + (itemsToProcess % groupSize != 0);
715 const uint32_t groupJobIdxEnd = groupSize < itemsToProcess ? groupSize : itemsToProcess;
719 auto groupJobFunc = [func = GAIA_MOV(job.
func), groupJobIdxEnd]() {
722 args.
idxEnd = groupJobIdxEnd;
725 auto handle = add(
Job{GAIA_MOV(groupJobFunc), prio, JobCreationFlags::Default});
732 auto callbackHandle = add_parallel_callback(GAIA_MOV(job.
func), jobs);
737 std::span<JobHandle> handles(pHandles, jobs + 1);
739 add_n(prio, handles);
741#if GAIA_ASSERT_ENABLED
742 for (
auto jobHandle: handles)
743 GAIA_ASSERT(m_jobManager.is_clear(jobHandle));
747 for (uint32_t jobIndex = 0; jobIndex < jobs; ++jobIndex) {
748 const uint32_t groupJobIdxStart = jobIndex * groupSize;
749 const uint32_t groupJobIdxEnd =
750 core::get_min(groupSize, itemsToProcess - groupJobIdxStart) + groupJobIdxStart;
752 auto& jobData = m_jobManager.data(pHandles[jobIndex]);
753 jobData.func = parallel_group_func(callbackHandle, groupJobIdxStart, groupJobIdxEnd);
758 auto& jobData = m_jobManager.data(pHandles[jobs]);
763 dep(handles.subspan(0, jobs), pHandles[jobs]);
768 return pHandles[jobs];
779 GAIA_ASSERT(main_thread());
780 GAIA_ASSERT(job.
pCtx !=
nullptr);
781 GAIA_ASSERT(job.
invoke !=
nullptr);
783 GAIA_ASSERT(itemsToProcess != 0);
784 if (itemsToProcess == 0)
787 if GAIA_UNLIKELY (m_stop)
790 const auto prio = job.
priority = final_prio(job);
792 if (groupSize == 0) {
793 const auto cntWorkers = core::get_max(1U, m_workersCnt[(uint32_t)prio]);
794 groupSize = itemsToProcess / cntWorkers + (itemsToProcess % cntWorkers != 0);
796 constexpr uint32_t maxUnitsOfWorkPerGroup = 8;
797 groupSize = groupSize / maxUnitsOfWorkPerGroup;
802 const auto jobs = itemsToProcess / groupSize + (itemsToProcess % groupSize != 0);
805 const uint32_t groupJobIdxEnd = groupSize < itemsToProcess ? groupSize : itemsToProcess;
806 auto handle = add(
Job{parallel_group_func(job, 0, groupJobIdxEnd), prio, JobCreationFlags::Default});
812 std::span<JobHandle> handles(pHandles, jobs + 1);
814 add_n(prio, handles);
816#if GAIA_ASSERT_ENABLED
817 for (
auto jobHandle: handles)
818 GAIA_ASSERT(m_jobManager.is_clear(jobHandle));
821 for (uint32_t jobIndex = 0; jobIndex < jobs; ++jobIndex) {
822 const uint32_t groupJobIdxStart = jobIndex * groupSize;
823 const uint32_t groupJobIdxEnd =
824 core::get_min(groupSize, itemsToProcess - groupJobIdxStart) + groupJobIdxStart;
826 auto& jobData = m_jobManager.data(pHandles[jobIndex]);
827 jobData.func = parallel_group_func(job, groupJobIdxStart, groupJobIdxEnd);
831 auto& jobData = m_jobManager.data(pHandles[jobs]);
835 dep(handles.subspan(0, jobs), pHandles[jobs]);
837 return pHandles[jobs];
848 GAIA_ASSERT(main_thread());
851 const auto& jobData = m_jobManager.data(jobHandle);
852 GAIA_ASSERT(jobData.data.gen == jobHandle.
gen());
853 return JobManager::done(jobData);
864 GAIA_PROF_SCOPE(tp::wait);
866 GAIA_ASSERT(main_thread());
872 auto* ctx = detail::tl_workerCtx;
873 auto& jobData = m_jobManager.data(jobHandle);
874 const bool waitBackground = is_background(jobData);
875 auto state = jobData.state.load(std::memory_order_acquire);
878 GAIA_ASSERT(state != 0);
881 for (; (state & JobState::STATE_BITS_MASK) < JobState::Done;
882 state = jobData.state.load(std::memory_order_acquire)) {
885 const bool canHelpBackground = waitBackground && m_backgroundWorkersCnt == 0;
886 const bool hasBackgroundJob = canHelpBackground && try_fetch_background_job(otherJobHandle);
887 const bool hasJob = hasBackgroundJob || try_fetch_job(*ctx, otherJobHandle);
889 if (run(otherJobHandle, ctx))
895 if ((state & JobState::STATE_BITS_MASK) == JobState::Executing) {
896 const auto workerId = (state & JobState::DEP_BITS_MASK);
897 auto* jobDoneEvent = &m_workersCtx[workerId].event;
898 jobDoneEvent->wait();
905 const auto workerBit = 1U << ctx->workerIdx;
906 const auto oldBlockedMask = m_blockedInWorkUntil.fetch_or(workerBit);
907 const auto newState = jobData.state.load();
908 if (newState == state)
909 Futex::wait(&m_blockedInWorkUntil, oldBlockedMask | workerBit, detail::WaitMaskAny);
910 m_blockedInWorkUntil.fetch_and(~workerBit);
917 GAIA_ASSERT(main_thread());
924 auto hwThreads = (uint32_t)std::thread::hardware_concurrency();
925 return core::get_max(1U, hwThreads);
931 uint32_t efficiencyCores = 0;
932#if GAIA_PLATFORM_APPLE
933 size_t size =
sizeof(efficiencyCores);
934 if (sysctlbyname(
"hw.perflevel1.logicalcpu", &efficiencyCores, &size,
nullptr, 0) != 0)
936#elif GAIA_PLATFORM_FREEBSD
940 size_t size =
sizeof(coreType);
942 GAIA_STRFMT(oidName,
sizeof(oidName),
"dev.cpu.%d.coretype", cpuIndex);
943 if (sysctlbyname(oidName, &coreType, &size,
nullptr, 0) != 0)
953#elif GAIA_PLATFORM_WINDOWS
954 const auto getProcessorInfo =
955 win32::kernel32_func<win32::GetLogicalProcessorInformationExFunc>(
"GetLogicalProcessorInformationEx");
956 if (getProcessorInfo ==
nullptr)
959 unsigned long length = 0;
962 if (!getProcessorInfo(win32::RelationProcessorCore,
nullptr, &length))
966 auto* pBuffer = (
char*)malloc(length);
967 if (pBuffer ==
nullptr)
971 if (!getProcessorInfo(win32::RelationProcessorCore, pBuffer, &length)) {
976 uint32_t heterogenousCnt = 0;
990 for (
const char* ptr = pBuffer; ptr < pBuffer + length;) {
991 int relationship = 0;
992 unsigned long size = 0;
993 memcpy(&relationship, ptr,
sizeof(relationship));
994 memcpy(&size, ptr +
sizeof(relationship),
sizeof(size));
997 if (relationship == win32::RelationProcessorCore) {
998 const auto efficiencyClass = (uint8_t)ptr[win32::ProcessorInfoEfficiencyClassOffset];
999 if (efficiencyClass == 0)
1007 if (heterogenousCnt == 0)
1008 efficiencyCores = 0;
1011#elif GAIA_PLATFORM_LINUX
1014 DIR* dir = opendir(
"/sys/devices/cpu_atom/cpus/");
1019 while ((entry = readdir(dir)) !=
nullptr) {
1020 if (strncmp(entry->d_name,
"cpu", 3) == 0 && entry->d_name[3] >=
'0' && entry->d_name[3] <=
'9')
1027 if (efficiencyCores == 0) {
1043 return efficiencyCores;
1047 static void* thread_func(
void* pCtx) {
1050 detail::tl_workerCtx = &ctx;
1055 ctx.tp->set_thread_name(ctx.workerIdx, ctx.prio);
1058 ctx.tp->set_thread_priority(ctx.workerIdx, ctx.prio);
1061 ctx.tp->worker_loop(ctx);
1063 detail::tl_workerCtx =
nullptr;
1072 void create_thread(uint32_t workerIdx, JobPriority prio,
bool background) {
1074 GAIA_ASSERT(workerIdx > 0);
1076 auto& ctx = m_workersCtx[workerIdx];
1078 ctx.workerIdx = workerIdx;
1080 ctx.background = background;
1081 ctx.threadCreated =
false;
1083#if GAIA_THREAD_PLATFORM == GAIA_THREAD_STD
1084 m_workers[workerIdx - 1] = std::thread([&ctx]() {
1085 thread_func((
void*)&ctx);
1088 pthread_attr_t attr{};
1089 int ret = pthread_attr_init(&attr);
1091 GAIA_LOG_W(
"pthread_attr_init failed for worker thread %u. ErrCode = %d", workerIdx, ret);
1112 if (prio == JobPriority::Low) {
1113 #if GAIA_PLATFORM_APPLE
1114 ret = pthread_attr_set_qos_class_np(&attr, QOS_CLASS_USER_INTERACTIVE, -9);
1117 "pthread_attr_set_qos_class_np failed for worker thread %u [prio=%u]. ErrCode = %d", workerIdx,
1118 (uint32_t)prio, ret);
1121 ret = pthread_attr_setschedpolicy(&attr, SCHED_OTHER);
1124 "pthread_attr_setschedpolicy SCHED_RR failed for worker thread %u [prio=%u]. ErrCode = %d", workerIdx,
1125 (uint32_t)prio, ret);
1128 int prioMax = core::get_min(38, sched_get_priority_max(SCHED_OTHER));
1129 int prioMin = core::get_min(prioMax, sched_get_priority_min(SCHED_OTHER));
1130 int prioUse = core::get_min(prioMin + 5, prioMax);
1131 prioUse = core::get_max(prioUse, prioMin);
1132 sched_param param{};
1133 param.sched_priority = prioUse;
1135 ret = pthread_attr_setschedparam(&attr, ¶m);
1138 "pthread_attr_setschedparam %d failed for worker thread %u [prio=%u]. ErrCode = %d",
1139 param.sched_priority, workerIdx, (uint32_t)prio, ret);
1143 ret = pthread_attr_setschedpolicy(&attr, SCHED_RR);
1146 "pthread_attr_setschedpolicy SCHED_RR failed for worker thread %u [prio=%u]. ErrCode = %d", workerIdx,
1147 (uint32_t)prio, ret);
1150 int prioMax = core::get_min(41, sched_get_priority_max(SCHED_RR));
1151 int prioMin = core::get_min(prioMax, sched_get_priority_min(SCHED_RR));
1152 int prioUse = core::get_max(prioMax - 5, prioMin);
1153 prioUse = core::get_min(prioUse, prioMax);
1154 sched_param param{};
1155 param.sched_priority = prioUse;
1157 ret = pthread_attr_setschedparam(&attr, ¶m);
1160 "pthread_attr_setschedparam %d failed for worker thread %u [prio=%u]. ErrCode = %d",
1161 param.sched_priority, workerIdx, (uint32_t)prio, ret);
1166 ret = pthread_create(&m_workers[workerIdx - 1], &attr, thread_func, (
void*)&ctx);
1168 GAIA_LOG_W(
"pthread_create failed for worker thread %u. ErrCode = %d", workerIdx, ret);
1170 ctx.threadCreated =
true;
1173 pthread_attr_destroy(&attr);
1177 set_thread_affinity(workerIdx);
1182 void join_thread(uint32_t workerIdx) {
1183 if GAIA_UNLIKELY (workerIdx > m_workers.
size())
1186#if GAIA_THREAD_PLATFORM == GAIA_THREAD_STD
1187 auto& t = m_workers[workerIdx - 1];
1191 auto& ctx = m_workersCtx[workerIdx];
1192 if (!ctx.threadCreated)
1195 auto& t = m_workers[workerIdx - 1];
1196 pthread_join(t,
nullptr);
1197 ctx.threadCreated =
false;
1201 void create_worker_threads(uint32_t& workerIdx, JobPriority prio, uint32_t count) {
1202 for (uint32_t i = 0; i < count; ++i)
1203 create_thread(workerIdx++, prio,
false);
1206 void create_background_worker_threads(uint32_t& workerIdx) {
1207 for (uint32_t i = 0; i < m_backgroundWorkersCnt; ++i)
1208 create_thread(workerIdx++, JobPriority::Low,
true);
1211 void set_thread_priority([[maybe_unused]] uint32_t workerIdx, [[maybe_unused]] JobPriority priority) {
1212#if GAIA_PLATFORM_WINDOWS
1213 void* nativeHandle = (
void*)m_workers[workerIdx - 1].native_handle();
1215 const auto setThreadInfo = win32::kernel32_func<win32::SetThreadInformationFunc>(
"SetThreadInformation");
1216 if (setThreadInfo ==
nullptr)
1219 win32::ThreadPowerThrottlingState state{};
1220 state.Version = win32::ThreadPowerThrottlingCurrentVersion;
1221 if (priority == JobPriority::High) {
1225 state.ControlMask = win32::ThreadPowerThrottlingExecutionSpeed;
1226 state.StateMask = 0;
1231 state.ControlMask = win32::ThreadPowerThrottlingExecutionSpeed;
1232 state.StateMask = win32::ThreadPowerThrottlingExecutionSpeed;
1235 const int ret = setThreadInfo(nativeHandle, win32::ThreadPowerThrottling, &state,
sizeof(state));
1237 GAIA_LOG_W(
"SetThreadInformation failed for thread %u", workerIdx);
1245 void set_thread_affinity([[maybe_unused]] uint32_t workerIdx) {
1285 void set_thread_name(uint32_t workerIdx, JobPriority prio) {
1286 const bool background = m_workersCtx[workerIdx].background;
1287 const char* workerKind = background ?
"BG" : prio == JobPriority::High ?
"HI" :
"LO";
1288#if GAIA_PROF_USE_PROFILER_THREAD_NAME
1289 char threadName[16]{};
1290 GAIA_STRFMT(threadName, 16,
"worker_%s_%u", workerKind, workerIdx);
1291 GAIA_PROF_THREAD_NAME(threadName);
1292#elif GAIA_PLATFORM_WINDOWS
1293 void* nativeHandle = (
void*)m_workers[workerIdx - 1].native_handle();
1294 const wchar_t* workerKindW = background ? L
"BG" : prio == JobPriority::High ? L
"HI" : L
"LO";
1296 const auto pSetThreadDescFunc = win32::kernel32_func<win32::SetThreadDescriptionFunc>(
"SetThreadDescription");
1297 if (pSetThreadDescFunc !=
nullptr) {
1298 wchar_t threadName[16]{};
1299 swprintf_s(threadName, L
"worker_%s_%u", workerKindW, workerIdx);
1301 const long hr = pSetThreadDescFunc(nativeHandle, threadName);
1303 GAIA_LOG_W(
"Issue setting name for worker %s thread %u!", workerKind, workerIdx);
1306#elif GAIA_PLATFORM_APPLE
1307 char threadName[16]{};
1308 GAIA_STRFMT(threadName, 16,
"worker_%s_%u", workerKind, workerIdx);
1309 auto ret = pthread_setname_np(threadName);
1311 GAIA_LOG_W(
"Issue setting name for worker %s thread %u!", workerKind, workerIdx);
1312#elif GAIA_PLATFORM_LINUX || GAIA_PLATFORM_FREEBSD
1313 auto nativeHandle = m_workers[workerIdx - 1];
1317 char threadName[32]{};
1318 GAIA_STRFMT(threadName, 32,
"worker_%s_%u", workerKind, workerIdx);
1319 GAIA_PROF_THREAD_NAME(threadName);
1321 auto ret = pthread_setname_np(nativeHandle, threadName);
1323 GAIA_LOG_W(
"Issue setting name for worker %s thread %u!", workerKind, workerIdx);
1329 GAIA_NODISCARD
bool main_thread()
const {
1330 return std::this_thread::get_id() == m_mainThreadId;
1335 void main_thread_tick() {
1336 auto& ctx = *detail::tl_workerCtx;
1340 JobHandle jobHandle;
1341 if (!try_fetch_job(ctx, jobHandle))
1344 (void)run(jobHandle, &ctx);
1353 GAIA_NODISCARD
bool try_steal_job(ThreadCtx& ctx, JobPriority prio, JobHandle& jobHandle) {
1354 const auto workerCnt = m_workersCtx.size();
1355 for (uint32_t i = 0; i < workerCnt;) {
1357 if (i == ctx.workerIdx || m_workersCtx[i].background || m_workersCtx[i].prio != prio) {
1362 const auto res = m_workersCtx[i].jobQueue.try_steal(jobHandle);
1370 if (jobHandle != (JobHandle)JobNull_t{})
1384 GAIA_NODISCARD
bool try_fetch_prio(ThreadCtx& ctx, JobPriority prio, JobHandle& jobHandle) {
1385 if (m_jobQueue[(uint32_t)prio].try_pop(jobHandle))
1388 return try_steal_job(ctx, prio, jobHandle);
1394 GAIA_NODISCARD
bool try_fetch_background_job(JobHandle& jobHandle) {
1395 return m_jobQueueBackground.try_pop(jobHandle);
1402 GAIA_NODISCARD
bool try_fetch_job(ThreadCtx& ctx, JobHandle& jobHandle) {
1404 return try_fetch_background_job(jobHandle);
1407 if (ctx.jobQueue.try_pop(jobHandle))
1411 if (ctx.workerIdx == 0) {
1412 if (try_fetch_prio(ctx, JobPriority::High, jobHandle))
1415 return try_fetch_prio(ctx, JobPriority::Low, jobHandle);
1418 return try_fetch_prio(ctx, ctx.prio, jobHandle);
1425 GAIA_NODISCARD
bool can_run_inline(
const ThreadCtx* ctx,
const JobContainer& jobData)
const {
1426 const bool background = is_background(jobData);
1428 return (ctx !=
nullptr && ctx->background) || m_backgroundWorkersCnt == 0;
1431 if (ctx ==
nullptr || ctx->workerIdx == 0)
1435 if (!ctx->background && ctx->prio == jobData.prio)
1440 return m_workerThreadsCnt[(uint32_t)jobData.prio] == 0;
1446 void wait_for_queue_space(ThreadCtx& ctx,
const JobContainer& jobData) {
1447 const bool background = is_background(jobData);
1452 if (m_backgroundWorkersCnt != 0)
1455 const auto prioIdx = (uint32_t)jobData.prio;
1456 if (m_workerThreadsCnt[prioIdx] != 0)
1461 JobHandle otherJobHandle;
1462 const bool hasWork =
1463 ctx.background ? try_fetch_background_job(otherJobHandle) : try_fetch_prio(ctx, ctx.prio, otherJobHandle);
1465 (void)run(otherJobHandle, &ctx);
1469 std::this_thread::yield();
1475 void worker_loop(ThreadCtx& ctx) {
1479 m_semBackground.
wait();
1481 m_sem[(uint32_t)ctx.prio].
wait();
1485 JobHandle jobHandle;
1486 if (!try_fetch_job(ctx, jobHandle))
1489 (void)run(jobHandle, detail::tl_workerCtx);
1493 const bool stop = m_stop.load();
1501 if (m_workers.
empty())
1508 GAIA_FOR(JobPriorityCnt) {
1509 if (m_workerThreadsCnt[i] != 0)
1510 m_sem[i].
release((int32_t)m_workerThreadsCnt[i]);
1512 if (m_backgroundWorkersCnt != 0)
1513 m_semBackground.
release((int32_t)m_backgroundWorkersCnt);
1515 auto* ctx = detail::tl_workerCtx;
1516 if (ctx ==
nullptr) {
1519 ctx = &m_workersCtx[0];
1520 detail::tl_workerCtx = ctx;
1524 JobHandle jobHandle;
1525 while (try_fetch_job(*ctx, jobHandle)) {
1526 run(jobHandle, ctx);
1528 while (try_fetch_background_job(jobHandle)) {
1529 run(jobHandle, ctx);
1532 detail::tl_workerCtx =
nullptr;
1535 GAIA_FOR(m_workers.
size()) join_thread(i + 1);
1538 m_stop.store(false);
1542 JobPriority final_frame_prio(JobPriority priority) {
1543 const auto cntWorkers = m_workersCnt[(uint32_t)priority];
1544 return cntWorkers > 0
1548 : (JobPriority)(((uint32_t)priority + 1U) % (uint32_t)JobPriorityCnt);
1552 JobPriority final_prio(
const Job& job) {
1553 if ((job.flags & JobCreationFlags::Background) != 0U)
1554 return job.priority;
1556 return final_frame_prio(job.priority);
1560 template <
typename TJob>
1561 JobPriority final_prio(
const TJob& job) {
1562 return final_frame_prio(job.priority);
1568 GAIA_NODISCARD
static bool is_background(
const JobContainer& jobData) {
1569 return (jobData.flags & JobCreationFlags::Background) != 0U;
1575 static void copy_edges(
const JobContainer& jobData, JobHandle* pEdges) {
1576 const auto cnt = jobData.edges.depCnt;
1578 pEdges[0] = jobData.edges.dep;
1581 GAIA_FOR(cnt) pEdges[i] = jobData.edges.pDeps[i];
1588 uint32_t signal_edges(std::span<const JobHandle> edges, JobHandle* pReadyHandles) {
1590 for (
const auto depHandle: edges) {
1591#if GAIA_LOG_JOB_STATES
1592 GAIA_LOG_N(
"SIGNAL -> %u.%u", depHandle.id(), depHandle.gen());
1594 auto& depData = m_jobManager.data(depHandle);
1595 if (!JobManager::signal_edge(depData))
1598 pReadyHandles[cnt++] = depHandle;
1607 void process(std::span<JobHandle> jobHandles, ThreadCtx* ctx) {
1608 auto* pHandles = (JobHandle*)alloca(
sizeof(JobHandle) * jobHandles.size());
1609 uint32_t handlesCnt = 0;
1611 for (
auto handle: jobHandles) {
1612 auto& jobData = m_jobManager.data(handle);
1613 m_jobManager.processing(jobData);
1621 if (!jobData.func.operator
bool())
1622 (void)run(handle, ctx);
1624 pHandles[handlesCnt++] = handle;
1627 std::span handles(pHandles, handlesCnt);
1628 while (!handles.empty()) {
1630 uint32_t pushed = 0;
1631 uint32_t released[JobPriorityCnt]{};
1632 uint32_t backgroundReleased = 0;
1633 for (; pushed < handles.size(); ++pushed) {
1634 const auto handle = handles[pushed];
1635 const auto& jobData = m_jobManager.data(handle);
1636 if (is_background(jobData)) {
1637 if (!m_jobQueueBackground.try_push(handle))
1640 ++backgroundReleased;
1644 const auto prio = jobData.prio;
1648 const bool useLocalQueue = ctx !=
nullptr && !ctx->background && ctx->workerIdx != 0 && ctx->prio == prio;
1650 useLocalQueue ? ctx->jobQueue.try_push(handle) : m_jobQueue[(uint32_t)prio].try_push(handle);
1654 released[(uint32_t)prio]++;
1657 GAIA_FOR(JobPriorityCnt) {
1660 const auto cnt = core::get_min(released[i], m_workerThreadsCnt[i]);
1662 m_sem[i].
release((int32_t)cnt);
1664 const auto backgroundCnt = core::get_min(backgroundReleased, m_backgroundWorkersCnt);
1665 if (backgroundCnt != 0)
1666 m_semBackground.
release((int32_t)backgroundCnt);
1668 handles = handles.subspan(pushed);
1669 if (!handles.empty()) {
1670 const auto handle = handles[0];
1671 const auto& jobData = m_jobManager.data(handle);
1673 if (can_run_inline(ctx, jobData)) {
1677 handles = handles.subspan(1);
1679 GAIA_ASSERT(ctx !=
nullptr);
1680 wait_for_queue_space(*ctx, jobData);
1686 bool run(JobHandle jobHandle, ThreadCtx* ctx) {
1687 if (jobHandle == (JobHandle)JobNull_t{})
1690 auto& jobData = m_jobManager.data(jobHandle);
1691 const bool manualDelete = (jobData.flags & JobCreationFlags::ManualDelete) != 0U;
1692 const bool canWait = (jobData.flags & JobCreationFlags::CanWait) != 0U;
1694 m_jobManager.executing(jobData, ctx->workerIdx);
1696 if (m_blockedInWorkUntil.load() != 0) {
1697 const auto blockedCnt = m_blockedInWorkUntil.exchange(0);
1698 if (blockedCnt != 0)
1699 Futex::wake(&m_blockedInWorkUntil, detail::WaitMaskAll);
1702 GAIA_ASSERT(jobData.idx != (uint32_t)-1 && jobData.data.gen != (uint32_t)-1);
1705 m_jobManager.run(jobData);
1712 jobData.func.finish();
1714 if (jobData.edges.depCnt == 0) {
1715 test_before_finalize(jobHandle);
1716 JobManager::finalize(jobData);
1720 const auto edgeCnt = jobData.edges.depCnt;
1721 auto* pEdges = (JobHandle*)alloca(
sizeof(JobHandle) * edgeCnt);
1722 copy_edges(jobData, pEdges);
1724 JobManager::free_edges(jobData);
1729 test_before_finalize(jobHandle);
1730 JobManager::finalize(jobData);
1732 const auto readyCnt = signal_edges(std::span<const JobHandle>(pEdges, edgeCnt), pEdges);
1733 process(std::span(pEdges, readyCnt), ctx);
1739 const auto* pFutexValue = &jobData.state;
1744 release_job(jobHandle);