Gaia-ECS v0.9.3
A simple and powerful entity component system
Loading...
Searching...
No Matches
jobmanager.h
1#pragma once
2
3#include "gaia/config/config.h"
4#include "gaia/config/profiler.h"
5
6#include <atomic>
7#include <cinttypes>
8// TODO: Currently necessary due to std::function. Replace them!
9#include <functional>
10
11#include "gaia/cnt/ilist.h"
12#include "gaia/core/span.h"
13#include "gaia/core/utility.h"
14#include "gaia/mem/mem_alloc.h"
15#include "gaia/mt/jobcommon.h"
16#include "gaia/mt/jobhandle.h"
17
18#define GAIA_LOG_JOB_STATES 0
19
20namespace gaia {
21 namespace mt {
22 enum JobState : uint32_t {
23 DEP_BITS_START = 0,
24 DEP_BITS = 27,
25 DEP_BITS_MASK = (uint32_t)((1u << DEP_BITS) - 1),
26
27 STATE_BITS_START = DEP_BITS_START + DEP_BITS,
28 STATE_BITS = 4,
29 STATE_BITS_MASK = (uint32_t)(((1u << STATE_BITS) - 1) << STATE_BITS_START),
30
31 // STATE
32
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
43 };
44
45 struct JobContainer;
46
47 namespace detail {
48 inline void signal_edge(JobContainer& jobData);
49 }
50
51 struct JobEdges {
54 union {
55 JobHandle dep;
56 JobHandle* pDeps;
57 };
59 uint32_t depCnt;
60 };
61
73 std::atomic_uint32_t state;
75 JobPriority prio;
77 JobCreationFlags flags;
81 std::function<void()> func;
82
83 JobContainer() = default;
84 ~JobContainer() = default;
85
86 JobContainer(const JobContainer& other): cnt::ilist_item(other) {
87 state = other.state.load();
88 prio = other.prio;
89 flags = other.flags;
90 edges = other.edges;
91 func = other.func;
92 }
93 JobContainer& operator=(const JobContainer& other) {
94 GAIA_ASSERT(core::addressof(other) != this);
95 cnt::ilist_item::operator=(other);
96 state = other.state.load();
97 prio = other.prio;
98 flags = other.flags;
99 edges = other.edges;
100 func = other.func;
101 return *this;
102 }
103
104 JobContainer(JobContainer&& other): cnt::ilist_item(GAIA_MOV(other)) {
105 state = other.state.load();
106 prio = other.prio;
107 flags = other.flags;
108 func = GAIA_MOV(other.func);
109
110 // if (edges.depCnt > 0)
111 // detail::signal_edge(*this);
112 edges = other.edges;
113 other.edges.depCnt = 0;
114 }
115 JobContainer& operator=(JobContainer&& other) {
116 GAIA_ASSERT(core::addressof(other) != this);
117 cnt::ilist_item::operator=(GAIA_MOV(other));
118 state = other.state.load();
119 prio = other.prio;
120 flags = other.flags;
121 func = GAIA_MOV(other.func);
122
123 // if (edges.depCnt > 0)
124 // detail::signal_edge(*this);
125 edges = other.edges;
126 other.edges.depCnt = 0;
127
128 return *this;
129 }
130
131 GAIA_NODISCARD static JobContainer create(uint32_t index, uint32_t generation, void* pCtx) {
132 auto* ctx = (JobAllocCtx*)pCtx;
133
134 JobContainer jc{};
135 jc.idx = index;
136 jc.data.gen = generation;
137 jc.prio = ctx->priority;
138
139 return jc;
140 }
141
142 GAIA_NODISCARD static JobHandle handle(const JobContainer& jc) {
143 return JobHandle(jc.idx, jc.data.gen, (jc.prio == JobPriority::Low) != 0);
144 }
145 };
146
150
151 public:
152 JobContainer& data(JobHandle jobHandle) {
153 return m_jobData[jobHandle.id()];
154 }
155 const JobContainer& data(JobHandle jobHandle) const {
156 return m_jobData[jobHandle.id()];
157 }
158
162 GAIA_NODISCARD JobHandle alloc_job(const Job& job) {
163 JobAllocCtx ctx{job.priority};
164
165 auto handle = m_jobData.alloc(&ctx);
166 auto& j = m_jobData[handle.id()];
167
168 // Make sure there is not state yet
169 GAIA_ASSERT(j.state == 0 || j.state == JobState::Released);
170
171 j.edges = {};
172 j.prio = ctx.priority;
173 j.state.store(0);
174 j.func = job.func;
175 j.flags = job.flags;
176 return handle;
177 }
178
183 void free_job(JobHandle jobHandle) {
184 auto& jobData = m_jobData.free(jobHandle);
185 GAIA_ASSERT(done(jobData));
186 jobData.state.store(JobState::Released);
187 }
188
190 void reset() {
191 m_jobData.clear();
192 }
193
196 static void run(JobContainer& jobData) {
197 if (jobData.func.operator bool())
198 jobData.func();
199
200 finalize(jobData);
201 }
202
203 static bool signal_edge(JobContainer& jobData) {
204 // Subtract from dependency counter
205 const auto state = jobData.state.fetch_sub(1) - 1;
206
207 // If the job is not submitted, we can't accept it
208 const auto s = state & JobState::STATE_BITS_MASK;
209 if (s != JobState::Submitted)
210 return false;
211
212 // If the job still has some dependencies left we can't accept it
213 const auto deps = state & JobState::DEP_BITS_MASK;
214 return deps == 0;
215 }
216
217 static void free_edges(JobContainer& jobData) {
218 // We only allocate an array for 2 and more dependencies
219 if (jobData.edges.depCnt <= 1)
220 return;
221
222 mem::AllocHelper::free(jobData.edges.pDeps);
223 // jobData.edges.depCnt = 0;
224 // jobData.edges.pDeps = nullptr;
225 }
226
232 void dep(JobHandle jobFirst, JobHandle jobSecond) {
233 dep(std::span(&jobFirst, 1), jobSecond);
234 }
235
241 void dep(std::span<JobHandle> jobsFirst, JobHandle jobSecond) {
242 GAIA_ASSERT(!jobsFirst.empty());
243
244 GAIA_PROF_SCOPE(JobManager::dep);
245
246 auto& secondData = data(jobSecond);
247
248#if GAIA_ASSERT_ENABLED
249 GAIA_ASSERT(!busy(const_cast<const JobContainer&>(secondData)));
250 for (auto jobFirst: jobsFirst) {
251 const auto& firstData = data(jobFirst);
252 GAIA_ASSERT(!busy(firstData));
253 }
254#endif
255
256 for (auto jobFirst: jobsFirst)
257 dep_internal(jobFirst, jobSecond);
258
259 // Tell jobSecond that it has new dependencies
260 // secondData.canWait = true;
261 const uint32_t cnt = (uint32_t)jobsFirst.size();
262 [[maybe_unused]] const uint32_t statePrev = secondData.state.fetch_add(cnt);
263 GAIA_ASSERT((statePrev & JobState::DEP_BITS_MASK) < DEP_BITS_MASK - 1);
264 }
265
272 void dep_refresh(std::span<JobHandle> jobsFirst, JobHandle jobSecond) {
273 GAIA_ASSERT(!jobsFirst.empty());
274
275 GAIA_PROF_SCOPE(JobManager::dep_refresh);
276
277 auto& secondData = data(jobSecond);
278
279#if GAIA_ASSERT_ENABLED
280 GAIA_ASSERT(!busy(const_cast<const JobContainer&>(secondData)));
281 for (auto jobFirst: jobsFirst) {
282 const auto& firstData = data(jobFirst);
283 GAIA_ASSERT(!busy(firstData));
284 }
285
286 for (auto jobFirst: jobsFirst)
287 dep_refresh_internal(jobFirst, jobSecond);
288#endif
289
290 // Tell jobSecond that it has new dependencies
291 // secondData.canWait = true;
292 const uint32_t cnt = (uint32_t)jobsFirst.size();
293 [[maybe_unused]] const uint32_t statePrev = secondData.state.fetch_add(cnt);
294 GAIA_ASSERT((statePrev & JobState::DEP_BITS_MASK) < DEP_BITS_MASK - 1);
295 }
296
297 static uint32_t submit(JobContainer& jobData) {
298 [[maybe_unused]] const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
299 GAIA_ASSERT(state < JobState::Submitted);
300 const auto val = jobData.state.fetch_add(JobState::Submitted) + (uint32_t)JobState::Submitted;
301#if GAIA_LOG_JOB_STATES
302 GAIA_LOG_N("JobHandle %u.%u - SUBMITTED", jobData.idx, jobData.gen);
303#endif
304 return val;
305 }
306
307 static void processing(JobContainer& jobData) {
308 GAIA_ASSERT(submitted(const_cast<const JobContainer&>(jobData)));
309 jobData.state.store(JobState::Processing);
310#if GAIA_LOG_JOB_STATES
311 GAIA_LOG_N("JobHandle %u.%u - PROCESSING", jobData.idx, jobData.gen);
312#endif
313 }
314
315 static void executing(JobContainer& jobData, uint32_t workerIdx) {
316 GAIA_ASSERT(processing(const_cast<const JobContainer&>(jobData)));
317 jobData.state.store(JobState::Executing | workerIdx);
318#if GAIA_LOG_JOB_STATES
319 GAIA_LOG_N("JobHandle %u.%u - EXECUTING", jobData.idx, jobData.gen);
320#endif
321 }
322
323 static void finalize(JobContainer& jobData) {
324 jobData.state.store(JobState::Done);
325#if GAIA_LOG_JOB_STATES
326 GAIA_LOG_N("JobHandle %u.%u - DONE", jobData.idx, jobData.gen);
327#endif
328 }
329
330 static void reset_state(JobContainer& jobData) {
331 [[maybe_unused]] const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
332 // The job needs to be either clear or finalize for us to allow a reset
333 GAIA_ASSERT(state == 0 || state == JobState::Done);
334 jobData.state.store(0);
335#if GAIA_LOG_JOB_STATES
336 GAIA_LOG_N("JobHandle %u.%u - RESET_STATE", jobData.idx, jobData.gen);
337#endif
338 }
339
340 GAIA_NODISCARD bool is_clear(JobHandle jobHandle) const {
341 const auto& jobData = data(jobHandle);
342 const auto state = jobData.state.load();
343 return state == 0;
344 }
345
346 GAIA_NODISCARD static bool is_clear(JobContainer& jobData) {
347 const auto state = jobData.state.load();
348 return state == 0;
349 }
350
351 GAIA_NODISCARD static bool submitted(const JobContainer& jobData) {
352 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
353 return state == JobState::Submitted;
354 }
355
356 GAIA_NODISCARD static bool processing(const JobContainer& jobData) {
357 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
358 return state == JobState::Processing;
359 }
360
361 GAIA_NODISCARD static bool busy(const JobContainer& jobData) {
362 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
363 return state == JobState::Executing || state == JobState::Processing;
364 }
365
366 GAIA_NODISCARD static bool done(const JobContainer& jobData) {
367 const auto state = jobData.state.load() & JobState::STATE_BITS_MASK;
368 return state == JobState::Done;
369 }
370
371 private:
372 void dep_internal(JobHandle jobFirst, JobHandle jobSecond) {
373 GAIA_ASSERT(jobFirst != (JobHandle)JobNull_t{});
374 GAIA_ASSERT(jobSecond != (JobHandle)JobNull_t{});
375
376 auto& firstData = data(jobFirst);
377 const auto depCnt0 = firstData.edges.depCnt;
378 const auto depCnt1 = ++firstData.edges.depCnt;
379
380#if GAIA_LOG_JOB_STATES
381 GAIA_LOG_N(
382 "DEP %u.%u, %u -> %u.%u", jobFirst.id(), jobFirst.gen(), firstData.edges.depCnt, jobSecond.id(),
383 jobSecond.gen());
384#endif
385
386 if (depCnt1 <= 1) {
387 firstData.edges.dep = jobSecond;
388 } else if (depCnt1 == 2) {
389 auto prev = firstData.edges.dep;
390 // TODO: Use custom allocator
391 firstData.edges.pDeps = mem::AllocHelper::alloc<JobHandle>(depCnt1);
392 firstData.edges.pDeps[0] = prev;
393 firstData.edges.pDeps[1] = jobSecond;
394 } else {
395 // Reallocate if the previous value was a power of 2
396 const bool isPow2 = core::is_pow2(depCnt0);
397 if (isPow2) {
398 const auto nextPow2 = depCnt0 << 1;
399 auto* pPrev = firstData.edges.pDeps;
400 // TODO: Use custom allocator
401 firstData.edges.pDeps = mem::AllocHelper::alloc<JobHandle>(nextPow2);
402 if (pPrev != nullptr) {
403 GAIA_FOR(depCnt0) firstData.edges.pDeps[i] = pPrev[i];
404 mem::AllocHelper::free(pPrev);
405 }
406 }
407
408 // Append new dependencies
409 firstData.edges.pDeps[depCnt0] = jobSecond;
410 }
411 }
412
413#if GAIA_ASSERT_ENABLED
414 void dep_refresh_internal(JobHandle jobFirst, JobHandle jobSecond) const {
415 GAIA_ASSERT(jobFirst != (JobHandle)JobNull_t{});
416 GAIA_ASSERT(jobSecond != (JobHandle)JobNull_t{});
417
418 const auto& firstData = data(jobFirst);
419 const auto depCnt = firstData.edges.depCnt;
420
421 if (depCnt <= 1) {
422 GAIA_ASSERT(firstData.edges.dep == jobSecond);
423 } else {
424 GAIA_ASSERT(firstData.edges.pDeps != nullptr);
425 bool found = false;
426 GAIA_FOR(firstData.edges.depCnt) {
427 if (firstData.edges.pDeps[i] == jobSecond) {
428 found = true;
429 break;
430 }
431 }
432 GAIA_ASSERT(found);
433 }
434 }
435#endif
436 };
437
438 namespace detail {
439 void signal_edge(JobContainer& jobData) {
440 JobManager::signal_edge(jobData);
441 }
442 } // namespace detail
443 } // namespace mt
444} // namespace gaia
Definition span_impl.h:99
Definition jobmanager.h:147
void reset()
Resets the job pool.
Definition jobmanager.h:190
void dep(std::span< JobHandle > jobsFirst, JobHandle jobSecond)
Makes jobSecond depend on the jobs listed in jobsFirst. This means jobSecond will not run until all j...
Definition jobmanager.h:241
static void run(JobContainer &jobData)
Execute the functor associated with the job container.
Definition jobmanager.h:196
void free_job(JobHandle jobHandle)
Invalidates jobHandle by resetting its index in the job pool. Every time a job is deallocated its gen...
Definition jobmanager.h:183
void dep(JobHandle jobFirst, JobHandle jobSecond)
Makes jobSecond depend on jobFirst. This means jobSecond will not run until jobFirst finishes.
Definition jobmanager.h:232
GAIA_NODISCARD JobHandle alloc_job(const Job &job)
Allocates a new job container identified by a unique JobHandle.
Definition jobmanager.h:162
void dep_refresh(std::span< JobHandle > jobsFirst, JobHandle jobSecond)
Makes jobSecond depend on the jobs listed in jobsFirst. This means jobSecond will not run until all j...
Definition jobmanager.h:272
Checks if endianess was detected correctly at compile-time.
Definition bitset.h:9
Definition ilist.h:14
uint32_t idx
Allocated items: Index in the list. Deleted items: Index of the next deleted item in the list.
Definition ilist.h:22
Implicit list. Rather than with pointers, items.
Definition ilist.h:76
TListItem & free(TItemHandle handle)
Invalidates handle. Every time an item is deallocated its generation is increased by one.
Definition ilist.h:235
GAIA_NODISCARD TItemHandle alloc(void *ctx)
Allocates a new item in the list.
Definition ilist.h:175
Definition jobcommon.h:29
Definition jobmanager.h:62
std::function< void()> func
Function to execute when running the job.
Definition jobmanager.h:81
JobPriority prio
Job priority.
Definition jobmanager.h:75
std::atomic_uint32_t state
Current state of the job Consist of upper and bottom part. Least significant bits = special purpose....
Definition jobmanager.h:73
JobEdges edges
Dependency graph.
Definition jobmanager.h:79
JobCreationFlags flags
Job flags.
Definition jobmanager.h:77
Definition jobmanager.h:51
uint32_t depCnt
Number of dependencies.
Definition jobmanager.h:59
Definition jobhandle.h:13
Definition jobcommon.h:33