3#include "gaia/config/config.h"
7#include "gaia/cnt/darray.h"
8#include "gaia/mem/smallblock_allocator.h"
9#include "gaia/mt/jobcommon.h"
10#include "gaia/mt/jobhandle.h"
11#include "gaia/mt/threadpool.h"
15 enum class QueryExecType : uint32_t;
25 enum class SchedFlags : uint8_t {
36 GAIA_NODISCARD
inline bool sched_flags_has(SchedFlags flags, SchedFlags flag) {
37 return ((uint8_t)flags & (uint8_t)flag) != 0U;
49 SchedFlags
flags = SchedFlags::Default;
57 void (*
invoke)(
void*
pCtx, uint32_t idxStart, uint32_t idxEnd) =
nullptr;
65 SchedFlags
flags = SchedFlags::Default;
121 void* m_pCleanupCtx =
nullptr;
122 void (*m_cleanup)(
void* pCtx) =
nullptr;
123 bool m_valid =
false;
124 bool m_submitted =
false;
125 bool m_waited =
false;
128 if (m_cleanup !=
nullptr) {
129 m_cleanup(m_pCleanupCtx);
131 m_pCleanupCtx =
nullptr;
135 GAIA_NODISCARD
static bool same_sched(
const Sched& a,
const Sched& b) {
149 m_sched(sched), m_token(
token), m_pCleanupCtx(pCleanupCtx), m_cleanup(cleanup), m_valid(true),
150 m_submitted(submitted) {}
163 m_sched(other.m_sched), m_token(other.m_token), m_pCleanupCtx(other.m_pCleanupCtx),
164 m_cleanup(other.m_cleanup), m_valid(other.m_valid), m_submitted(other.m_submitted), m_waited(other.m_waited) {
165 other.m_valid =
false;
166 other.m_cleanup =
nullptr;
167 other.m_pCleanupCtx =
nullptr;
177 m_sched = other.m_sched;
178 m_token = other.m_token;
179 m_pCleanupCtx = other.m_pCleanupCtx;
180 m_cleanup = other.m_cleanup;
181 m_valid = other.m_valid;
182 m_submitted = other.m_submitted;
183 m_waited = other.m_waited;
184 other.m_valid =
false;
185 other.m_cleanup =
nullptr;
186 other.m_pCleanupCtx =
nullptr;
224 inline mt::JobPriority exec_prio(QueryExecType execType) {
227 return (uint32_t)execType == 3U ? mt::JobPriority::Low : mt::JobPriority::High;
230 inline bool sched_flags_background(SchedFlags flags) {
231 return sched_flags_has(flags, SchedFlags::Background);
234 inline mt::JobCreationFlags job_creation_flags(SchedFlags flags) {
235 uint8_t jobFlags = (uint8_t)mt::JobCreationFlags::ManualDelete;
236 if (sched_flags_background(flags))
237 jobFlags |= (uint8_t)mt::JobCreationFlags::Background;
238 return (mt::JobCreationFlags)jobFlags;
241 enum class SchedTokenKind : uint32_t { None, Single, Parallel };
243 struct SchedTokenDefData {
244 cnt::darray<mt::JobHandle> handles;
245 SchedTokenKind kind = SchedTokenKind::None;
246 bool submitted =
false;
248 GAIA_USE_SMALLBLOCK(SchedTokenDefData)
251 inline SchedToken make_sched_token(SchedTokenDefData* pData) {
253 token.
value[0] = (uintptr_t)pData;
257 inline SchedTokenDefData* sched_token_data(SchedToken token) {
258 return reinterpret_cast<SchedTokenDefData*
>(token.value[0]);
261 inline SchedToken add_one_def([[maybe_unused]]
void* pCtx,
const SchedTaskDesc* pDesc) {
262 GAIA_ASSERT(pDesc !=
nullptr);
263 GAIA_ASSERT(pDesc->invoke !=
nullptr);
264 if (pDesc ==
nullptr || pDesc->invoke ==
nullptr)
267 auto* pData =
new SchedTokenDefData();
268 pData->kind = SchedTokenKind::Single;
269 pData->handles.resize(1);
272 job.priority = exec_prio(pDesc->execType);
273 job.flags = job_creation_flags(pDesc->flags);
274 job.func = [pCtx = pDesc->pCtx, invoke = pDesc->invoke]() {
279 return make_sched_token(pData);
282 inline SchedToken sched_one_def([[maybe_unused]]
void* pCtx,
const SchedTaskDesc* pDesc) {
283 auto token = add_one_def(pCtx, pDesc);
284 auto* pData = sched_token_data(token);
285 if (pData !=
nullptr) {
287 pData->submitted =
true;
292 inline SchedToken add_par_def([[maybe_unused]]
void* pCtx,
const SchedParDesc* pDesc) {
293 GAIA_ASSERT(pDesc !=
nullptr);
294 GAIA_ASSERT(pDesc->invoke !=
nullptr);
295 if (pDesc ==
nullptr || pDesc->invoke ==
nullptr || pDesc->itemCount == 0)
299 const auto prio = exec_prio(pDesc->execType);
300 uint32_t groupSize = pDesc->groupSize;
301 if (groupSize == 0) {
302 const bool background = sched_flags_background(pDesc->flags);
304 background ? core::get_max(1U, tp.background_workers()) : core::get_max(1U, tp.workers() + 1U);
305 groupSize = (pDesc->itemCount + workers - 1) / workers;
306 constexpr uint32_t maxUnitsOfWorkPerGroup = 8;
307 groupSize = groupSize / maxUnitsOfWorkPerGroup;
312 const auto jobs = (pDesc->itemCount + groupSize - 1) / groupSize;
313 auto* pData =
new SchedTokenDefData();
314 pData->kind = SchedTokenKind::Parallel;
315 pData->handles.resize(jobs + 1);
317 for (uint32_t jobIndex = 0; jobIndex < jobs; ++jobIndex) {
318 const uint32_t idxStart = jobIndex * groupSize;
319 const uint32_t idxEnd = core::get_min(idxStart + groupSize, pDesc->itemCount);
323 job.flags = job_creation_flags(pDesc->flags);
324 job.func = [desc = *pDesc, idxStart, idxEnd]() {
325 desc.invoke(desc.pCtx, idxStart, idxEnd);
327 pData->handles[jobIndex] = tp.add(GAIA_MOV(job));
331 syncJob.priority = prio;
332 syncJob.flags = job_creation_flags(pDesc->flags);
333 pData->handles[jobs] = tp.add(GAIA_MOV(syncJob));
335 tp.dep(std::span(pData->handles.data(), jobs), pData->handles[jobs]);
336 return make_sched_token(pData);
339 inline SchedToken sched_par_def([[maybe_unused]]
void* pCtx,
const SchedParDesc* pDesc) {
340 auto token = add_par_def(pCtx, pDesc);
341 auto* pData = sched_token_data(token);
342 if (pData !=
nullptr) {
344 pData->submitted =
true;
349 inline void sched_submit_def([[maybe_unused]]
void* pCtx, SchedToken token) {
350 auto* pData = sched_token_data(token);
351 if (pData ==
nullptr || pData->submitted)
354 tp.submit(std::span(pData->handles.data(), pData->handles.size()));
355 pData->submitted =
true;
358 inline void sched_dep_def([[maybe_unused]]
void* pCtx, SchedToken tokenFirst, SchedToken tokenSecond) {
359 auto* pFirst = sched_token_data(tokenFirst);
360 auto* pSecond = sched_token_data(tokenSecond);
361 if (pFirst ==
nullptr || pSecond ==
nullptr || pFirst->handles.empty() || pSecond->handles.empty())
364 const auto firstDone = pFirst->handles.back();
365 if (pSecond->kind == SchedTokenKind::Parallel && pSecond->handles.size() > 1) {
366 const auto childCount = (uint32_t)pSecond->handles.size() - 1;
367 for (uint32_t i = 0; i < childCount; ++i)
368 tp.dep(firstDone, pSecond->handles[i]);
371 tp.dep(firstDone, pSecond->handles.back());
374 inline void sched_wait_def([[maybe_unused]]
void* pCtx, SchedToken token) {
375 auto* pData = sched_token_data(token);
376 if (pData ==
nullptr || pData->handles.empty())
381 inline void sched_del_def([[maybe_unused]]
void* pCtx, [[maybe_unused]] SchedToken token) {
382 auto* pData = sched_token_data(token);
383 if (pData ==
nullptr)
386 for (
auto handle: pData->handles) {
387 if (handle != mt::JobNull)
397 GAIA_NODISCARD
inline const Sched& sched_def() {
398 static const Sched sched = [] {
400 b.sched = &detail::sched_one_def;
401 b.sched_par = &detail::sched_par_def;
402 b.add = &detail::add_one_def;
403 b.add_par = &detail::add_par_def;
404 b.submit = &detail::sched_submit_def;
405 b.dep = &detail::sched_dep_def;
406 b.wait = &detail::sched_wait_def;
407 b.del = &detail::sched_del_def;
416 GAIA_NODISCARD
inline const Sched& sched_resolve(
const Sched& sched) {
417 if (sched.sched ==
nullptr && sched.sched_par ==
nullptr && sched.add ==
nullptr && sched.add_par ==
nullptr &&
418 sched.submit ==
nullptr && sched.dep ==
nullptr && sched.wait ==
nullptr && sched.del ==
nullptr)
427 GAIA_NODISCARD
inline SchedToken sched_one(
const Sched& sched,
const SchedTaskDesc& desc) {
428 const auto& resolved = sched_resolve(sched);
429 if (resolved.sched !=
nullptr)
430 return resolved.sched(resolved.pCtx, &desc);
431 GAIA_ASSERT(resolved.add !=
nullptr);
432 GAIA_ASSERT(resolved.submit !=
nullptr);
433 const auto token = resolved.add !=
nullptr ? resolved.add(resolved.pCtx, &desc) : SchedToken{};
434 if (resolved.submit !=
nullptr)
435 resolved.submit(resolved.pCtx, token);
446 GAIA_NODISCARD
inline SchedJob
447 sched_add(
const Sched& sched,
const SchedTaskDesc& desc,
void* pCleanupCtx,
void (*cleanup)(
void* pCtx)) {
448 const auto& resolved = sched_resolve(sched);
449 if (resolved.add !=
nullptr) {
450 if (resolved.submit ==
nullptr || resolved.wait ==
nullptr || resolved.del ==
nullptr) {
452 if (cleanup !=
nullptr)
453 cleanup(pCleanupCtx);
456 return SchedJob(resolved, resolved.add(resolved.pCtx, &desc),
false, pCleanupCtx, cleanup);
459 if (resolved.sched ==
nullptr || resolved.wait ==
nullptr || resolved.del ==
nullptr) {
461 if (cleanup !=
nullptr)
462 cleanup(pCleanupCtx);
465 return SchedJob(resolved, resolved.sched(resolved.pCtx, &desc),
true, pCleanupCtx, cleanup);
472 GAIA_NODISCARD
inline SchedToken sched_par(
const Sched& sched,
const SchedParDesc& desc) {
473 const auto& resolved = sched_resolve(sched);
474 if (resolved.sched_par !=
nullptr)
475 return resolved.sched_par(resolved.pCtx, &desc);
476 GAIA_ASSERT(resolved.add_par !=
nullptr);
477 GAIA_ASSERT(resolved.submit !=
nullptr);
478 const auto token = resolved.add_par !=
nullptr ? resolved.add_par(resolved.pCtx, &desc) : SchedToken{};
479 if (resolved.submit !=
nullptr)
480 resolved.submit(resolved.pCtx, token);
491 GAIA_NODISCARD
inline SchedJob
492 sched_add_par(
const Sched& sched,
const SchedParDesc& desc,
void* pCleanupCtx,
void (*cleanup)(
void* pCtx)) {
493 const auto& resolved = sched_resolve(sched);
494 if (resolved.add_par !=
nullptr) {
495 if (resolved.submit ==
nullptr || resolved.wait ==
nullptr || resolved.del ==
nullptr) {
497 if (cleanup !=
nullptr)
498 cleanup(pCleanupCtx);
501 return SchedJob(resolved, resolved.add_par(resolved.pCtx, &desc),
false, pCleanupCtx, cleanup);
504 if (resolved.sched_par ==
nullptr || resolved.wait ==
nullptr || resolved.del ==
nullptr) {
506 if (cleanup !=
nullptr)
507 cleanup(pCleanupCtx);
510 return SchedJob(resolved, resolved.sched_par(resolved.pCtx, &desc),
true, pCleanupCtx, cleanup);
516 inline void sched_submit(
const Sched& sched, SchedToken token) {
517 const auto& resolved = sched_resolve(sched);
518 if (resolved.submit !=
nullptr)
519 resolved.submit(resolved.pCtx, token);
526 inline void sched_dep(
const Sched& sched, SchedToken tokenFirst, SchedToken tokenSecond) {
527 const auto& resolved = sched_resolve(sched);
528 if (resolved.dep !=
nullptr)
529 resolved.dep(resolved.pCtx, tokenFirst, tokenSecond);
535 inline void sched_wait(
const Sched& sched, SchedToken token) {
536 const auto& resolved = sched_resolve(sched);
537 if (resolved.wait !=
nullptr)
538 resolved.wait(resolved.pCtx, token);
544 inline void sched_del(
const Sched& sched, SchedToken token) {
545 const auto& resolved = sched_resolve(sched);
546 if (resolved.del !=
nullptr)
547 resolved.del(resolved.pCtx, token);
551 if (!m_valid || m_submitted)
553 GAIA_ASSERT(m_sched.
submit !=
nullptr);
554 if (m_sched.
submit ==
nullptr)
556 sched_submit(m_sched, m_token);
561 if (!m_valid || !jobFirst.m_valid)
563 GAIA_ASSERT(same_sched(m_sched, jobFirst.m_sched));
564 GAIA_ASSERT(!m_submitted && !jobFirst.m_submitted);
565 if (!same_sched(m_sched, jobFirst.m_sched) || m_submitted || jobFirst.m_submitted)
567 sched_dep(m_sched, jobFirst.m_token, m_token);
571 if (!m_valid || m_waited)
573 GAIA_ASSERT(m_submitted);
576 sched_wait(m_sched, m_token);
584 if (m_submitted && !m_waited)
586 sched_del(m_sched, m_token);
Move-only wrapper for scheduler-owned ECS work.
Definition sched.h:118
GAIA_NODISCARD bool valid() const
Returns true if this wrapper owns scheduler work.
Definition sched.h:192
void submit()
Submits the added work if it has not been submitted yet.
Definition sched.h:550
SchedJob & operator=(SchedJob &&other) noexcept
Move-assigns a scheduler job wrapper.
Definition sched.h:173
GAIA_NODISCARD SchedToken token() const
Returns the opaque scheduler token.
Definition sched.h:198
void del()
Deletes scheduler resources and runs wrapper cleanup.
Definition sched.h:581
SchedJob(Sched sched, SchedToken token, bool submitted, void *pCleanupCtx, void(*cleanup)(void *pCtx))
Definition sched.h:148
void wait()
Waits for submitted work to complete.
Definition sched.h:570
~SchedJob()
Waits for and deletes a still-owned token.
Definition sched.h:153
void dep(const SchedJob &jobFirst)
Adds a dependency edge so this job runs after jobFirst.
Definition sched.h:560
SchedJob(SchedJob &&other) noexcept
Move-constructs a scheduler job wrapper.
Definition sched.h:162
JobHandle add(TJob job)
Creates a threadpool job from job.
Definition threadpool.h:384
static ThreadPool & get()
Returns the process-wide thread-pool instance.
Definition threadpool.h:161
void submit(std::span< JobHandle > jobHandles)
Pushes jobHandles into the internal queue so worker threads can pick them up and execute them....
Definition threadpool.h:466
void wait(JobHandle jobHandle)
Wait until a job associated with the jobHandle finishes executing. Cleans up any job allocations and ...
Definition threadpool.h:814
Description of a parallel-for submission to a scheduler.
Definition sched.h:53
void * pCtx
Opaque callback context forwarded to invoke().
Definition sched.h:55
void(* invoke)(void *pCtx, uint32_t idxStart, uint32_t idxEnd)
Parallel-for entry point receiving a half-open item range [idxStart, idxEnd).
Definition sched.h:57
uint32_t groupSize
Preferred group size. A value of 0 lets the scheduler choose.
Definition sched.h:61
SchedFlags flags
Scheduler flags describing non-default execution requirements.
Definition sched.h:65
QueryExecType execType
Execution hint selected by the scheduler caller.
Definition sched.h:63
uint32_t itemCount
Total number of items to process.
Definition sched.h:59
Description of a single task submitted to a scheduler.
Definition sched.h:41
SchedFlags flags
Scheduler flags describing non-default execution requirements.
Definition sched.h:49
void(* invoke)(void *pCtx)
Task entry point.
Definition sched.h:45
QueryExecType execType
Execution hint selected by the scheduler caller.
Definition sched.h:47
void * pCtx
Opaque callback context forwarded to invoke().
Definition sched.h:43
Opaque synchronization token returned by a scheduler. The scheduler owns the meaning of the payload.
Definition sched.h:19
uintptr_t value[2]
Scheduler-defined token payload.
Definition sched.h:21
Scheduler descriptor used by ECS runtime code. All callbacks may be null when the descriptor is only ...
Definition sched.h:71
SchedToken(* add_par)(void *pCtx, const SchedParDesc *pDesc)
Adds a parallel-for workload without submitting it for execution.
Definition sched.h:93
void * pCtx
Opaque scheduler-owned context passed back to every callback.
Definition sched.h:73
void(* dep)(void *pCtx, SchedToken tokenFirst, SchedToken tokenSecond)
Adds a dependency edge so tokenSecond runs after tokenFirst.
Definition sched.h:102
void(* wait)(void *pCtx, SchedToken token)
Waits until the scheduled work referenced by token finishes.
Definition sched.h:106
void(* del)(void *pCtx, SchedToken token)
Deletes any scheduler-owned resources associated with token.
Definition sched.h:110
SchedToken(* sched)(void *pCtx, const SchedTaskDesc *pDesc)
Schedules one task for execution.
Definition sched.h:78
void(* submit)(void *pCtx, SchedToken token)
Submits a previously added token.
Definition sched.h:97
SchedToken(* add)(void *pCtx, const SchedTaskDesc *pDesc)
Adds one task without submitting it for execution.
Definition sched.h:88
SchedToken(* sched_par)(void *pCtx, const SchedParDesc *pDesc)
Schedules a parallel-for workload for execution.
Definition sched.h:83