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