Gaia-ECS v1.0.0
A simple and powerful entity component system
Loading...
Searching...
No Matches
jobcommon.h
1#pragma once
2
3#include <cstddef>
4#include <inttypes.h>
5#include <new>
6#include <type_traits>
7#include <utility>
8
9#include "gaia/core/utility.h"
10#include "gaia/mem/mem_alloc.h"
11#include "gaia/mt/event.h"
12#include "gaia/mt/jobqueue.h"
13
14namespace gaia {
15 namespace util {
16 class SmallFunc;
17 template <typename Signature>
18 class MoveFunc;
19 } // namespace util
20
21 namespace mt {
23 enum class JobPriority : uint8_t {
25 High = 0,
27 Low = 1
28 };
30 static inline constexpr uint32_t JobPriorityCnt = 2;
31
33 enum JobCreationFlags : uint8_t {
35 Default = 0,
37 ManualDelete = 0x01,
39 CanWait = 0x02,
41 Background = 0x04
42 };
43
45 struct JobAllocCtx {
47 JobPriority priority;
48 };
49
51 struct JobArgs {
53 uint32_t idxStart;
55 uint32_t idxEnd;
56 };
57
59 namespace detail {
60 template <typename T>
61 struct is_move_func: std::false_type {};
62 template <typename Signature>
63 struct is_move_func<util::MoveFunc<Signature>>: std::true_type {};
64 } // namespace detail
66
72 template <typename TArg>
74 public:
76 static constexpr uint32_t InlineSize = 24;
77
78 private:
79 enum class Op : uint8_t { Invoke, Finish, Destroy, Move };
80 using OpFn = void (*)(Op op, JobCallable* dst, JobCallable* src, const void* pArg);
81
82 OpFn m_func = nullptr;
83 alignas(std::max_align_t) uint8_t m_storage[InlineSize];
84
86 static void op_storage(Op op, JobCallable* dst, JobCallable* src, [[maybe_unused]] const void* pArg) {
87 auto*& pStorage = *reinterpret_cast<void**>(dst->m_storage);
88 switch (op) {
89 case Op::Invoke:
90 GAIA_ASSERT(false && "The callable was already destroyed by finish()");
91 break;
92 case Op::Finish:
93 break;
94 case Op::Destroy:
95 mem::AllocHelper::free(pStorage);
96 break;
97 case Op::Move:
98 GAIA_ASSERT(src != nullptr);
99 pStorage = *reinterpret_cast<void**>(src->m_storage);
100 dst->m_func = src->m_func;
101 src->m_func = nullptr;
102 break;
103 }
104 }
105
106 template <typename Fn>
107 static constexpr bool invocable() {
108 if constexpr (std::is_void_v<TArg>)
109 return std::is_invocable_r_v<void, Fn&>;
110 else
111 return std::is_invocable_r_v<void, Fn&, const TArg&>;
112 }
113
114 template <typename Fn>
115 static void invoke(Fn& fn, [[maybe_unused]] const void* pArg) {
116 if constexpr (std::is_void_v<TArg>)
117 fn();
118 else {
119 GAIA_ASSERT(pArg != nullptr);
120 fn(*static_cast<const TArg*>(pArg));
121 }
122 }
123
124 void destroy() {
125 if (m_func != nullptr) {
126 m_func(Op::Destroy, this, nullptr, nullptr);
127 m_func = nullptr;
128 }
129 }
130
131 void exec_inter(const void* pArg) const {
132 GAIA_ASSERT(m_func != nullptr);
133 m_func(Op::Invoke, const_cast<JobCallable*>(this), nullptr, pArg);
134 }
135
136 template <typename F>
137 void init(F&& f) {
138 using Fn = std::decay_t<F>;
139 static_assert(
140 !std::is_same_v<Fn, util::SmallFunc> && !detail::is_move_func<Fn>::value,
141 "Pass the callable itself. SmallFunc and MoveFunc may keep it in SmallBlockAllocator, which job "
142 "threads must not free from");
143 static_assert(invocable<Fn>(), "JobCallable requires a callable compatible with its signature");
144 static_assert(std::is_move_constructible_v<Fn>, "Callable must be move-constructible");
145 static_assert(
146 alignof(Fn) <= alignof(std::max_align_t), "Over-aligned callables are not supported for JobCallable");
147
148 if constexpr (stores_inline<Fn>()) {
149 new (m_storage) Fn(GAIA_FWD(f));
150
151 m_func = [](Op op, JobCallable* dst, JobCallable* src, const void* pArg) {
152 auto* pFn = reinterpret_cast<Fn*>(dst->m_storage);
153 switch (op) {
154 case Op::Invoke:
155 invoke(*pFn, pArg);
156 break;
157 case Op::Finish:
158 // A trivially destructible callable has nothing to end and is left untouched.
159 if constexpr (!std::is_trivially_destructible_v<Fn>) {
160 pFn->~Fn();
161 dst->m_func = nullptr;
162 }
163 break;
164 case Op::Destroy:
165 if constexpr (!std::is_trivially_destructible_v<Fn>)
166 pFn->~Fn();
167 break;
168 case Op::Move: {
169 GAIA_ASSERT(src != nullptr);
170 auto* pSrcFn = reinterpret_cast<Fn*>(src->m_storage);
171 new (dst->m_storage) Fn(GAIA_MOV(*pSrcFn));
172 if constexpr (!std::is_trivially_destructible_v<Fn>)
173 pSrcFn->~Fn();
174 dst->m_func = src->m_func;
175 src->m_func = nullptr;
176 break;
177 }
178 }
179 };
180 } else {
181 auto* pStorage = mem::AllocHelper::alloc<Fn>();
182 GAIA_ASSERT((uintptr_t)pStorage % alignof(Fn) == 0);
183 auto* pFunc = new (pStorage) Fn(GAIA_FWD(f));
184 *reinterpret_cast<Fn**>(m_storage) = pFunc;
185
186 m_func = [](Op op, JobCallable* dst, JobCallable* src, const void* pArg) {
187 auto*& pFn = *reinterpret_cast<Fn**>(dst->m_storage);
188 switch (op) {
189 case Op::Invoke:
190 GAIA_ASSERT(pFn != nullptr);
191 invoke(*pFn, pArg);
192 break;
193 case Op::Finish:
194 // A trivially destructible callable has nothing to end and is left untouched.
195 if constexpr (!std::is_trivially_destructible_v<Fn>) {
196 GAIA_ASSERT(pFn != nullptr);
197 pFn->~Fn();
198 dst->m_func = &op_storage;
199 }
200 break;
201 case Op::Destroy:
202 GAIA_ASSERT(pFn != nullptr);
203 if constexpr (!std::is_trivially_destructible_v<Fn>)
204 pFn->~Fn();
206 break;
207 case Op::Move:
208 GAIA_ASSERT(src != nullptr);
209 *reinterpret_cast<Fn**>(dst->m_storage) = *reinterpret_cast<Fn**>(src->m_storage);
210 dst->m_func = src->m_func;
211 *reinterpret_cast<Fn**>(src->m_storage) = nullptr;
212 src->m_func = nullptr;
213 break;
214 }
215 };
216 }
217 }
218
219 public:
223 template <typename F>
224 GAIA_NODISCARD static constexpr bool stores_inline() {
225 using Fn = std::decay_t<F>;
226 return sizeof(Fn) <= InlineSize && alignof(Fn) <= alignof(std::max_align_t);
227 }
228
229 JobCallable() = default;
230 ~JobCallable() {
231 if (m_func != nullptr)
232 m_func(Op::Destroy, this, nullptr, nullptr);
233 }
234
235 JobCallable(const JobCallable&) = delete;
236 JobCallable& operator=(const JobCallable&) = delete;
237
240 JobCallable(JobCallable&& other) noexcept {
241 if (other.m_func != nullptr)
242 other.m_func(Op::Move, this, &other, nullptr);
243 }
244
248 JobCallable& operator=(JobCallable&& other) noexcept {
249 if (this != &other) {
250 destroy();
251 if (other.m_func != nullptr)
252 other.m_func(Op::Move, this, &other, nullptr);
253 }
254 return *this;
255 }
256
260 template <typename F, typename = std::enable_if_t<!std::is_same_v<std::decay_t<F>, JobCallable>>>
261 JobCallable(F&& f) {
262 init(GAIA_FWD(f));
263 }
264
269 template <typename F, typename = std::enable_if_t<!std::is_same_v<std::decay_t<F>, JobCallable>>>
271 destroy();
272 init(GAIA_FWD(f));
273 return *this;
274 }
275
280 template <typename F>
281 static JobCallable create(F&& f) {
282 JobCallable func;
283 func.init(GAIA_FWD(f));
284 return func;
285 }
286
288 template <typename T = TArg, typename = std::enable_if_t<std::is_void_v<T>>>
289 void exec() const {
290 exec_inter(nullptr);
291 }
292
295 template <typename T = TArg, typename = std::enable_if_t<!std::is_void_v<T>>>
296 void exec(const T& arg) const {
297 exec_inter(&arg);
298 }
299
301 template <typename T = TArg, typename = std::enable_if_t<std::is_void_v<T>>>
302 void operator()() const {
303 exec_inter(nullptr);
304 }
305
308 template <typename T = TArg, typename = std::enable_if_t<!std::is_void_v<T>>>
309 void operator()(const T& arg) const {
310 exec_inter(&arg);
311 }
312
314 void reset() {
315 destroy();
316 }
317
322 void finish() {
323 if (m_func != nullptr)
324 m_func(Op::Finish, this, nullptr, nullptr);
325 }
326
329 explicit operator bool() const {
330 return m_func != nullptr;
331 }
332 };
333
335 using JobFunc = JobCallable<void>;
337 using JobArgsFunc = JobCallable<JobArgs>;
338
340 struct Job {
348 JobPriority priority = JobPriority::High;
350 JobCreationFlags flags = JobCreationFlags::Default;
351 };
352
354 struct JobParallel {
358 JobPriority priority = JobPriority::High;
359 };
360
365 void* pCtx = nullptr;
367 void (*invoke)(void*, const JobArgs&) = nullptr;
369 JobPriority priority = JobPriority::High;
370 };
371
372 class ThreadPool;
373
375 struct ThreadCtx {
379 uint32_t workerIdx;
381 JobPriority prio;
383 bool background = false;
385 bool threadCreated = false;
389 JobQueue<512> jobQueue;
390
391 ThreadCtx() = default;
392 ~ThreadCtx() = default;
393
394 void reset() {
395 background = false;
396 threadCreated = false;
397 event.reset();
398 jobQueue.clear();
399 }
400
401 ThreadCtx(const ThreadCtx& other) = delete;
402 ThreadCtx& operator=(const ThreadCtx& other) = delete;
403 ThreadCtx(ThreadCtx&& other) = delete;
404 ThreadCtx& operator=(ThreadCtx&& other) = delete;
405 };
406 } // namespace mt
407} // namespace gaia
Auto-reset synchronization primitive for waking one waiting thread. A successful wait consumes the si...
Definition event.h:20
Move-only type-erased callable owned by a job. Callables of up to InlineSize bytes are stored inline....
Definition jobcommon.h:73
void operator()(const T &arg) const
Invokes the stored callable with arg.
Definition jobcommon.h:309
JobCallable & operator=(JobCallable &&other) noexcept
Destroys the stored callable and takes over the callable of other, leaving it empty.
Definition jobcommon.h:248
static constexpr uint32_t InlineSize
Size in bytes of the inline callable storage.
Definition jobcommon.h:76
void exec(const T &arg) const
Invokes the stored callable with arg.
Definition jobcommon.h:296
JobCallable(JobCallable &&other) noexcept
Takes over the callable of other, leaving it empty.
Definition jobcommon.h:240
void finish()
Ends the lifetime of the stored callable and everything it captured, but keeps any heap storage until...
Definition jobcommon.h:322
void reset()
Destroys the stored callable and everything it captured, and releases its storage.
Definition jobcommon.h:314
JobCallable & operator=(F &&f)
Destroys the stored callable and wraps the callable f.
Definition jobcommon.h:270
JobCallable(F &&f)
Wraps the callable f.
Definition jobcommon.h:261
static JobCallable create(F &&f)
Creates a wrapper for the callable f.
Definition jobcommon.h:281
void exec() const
Invokes the stored callable.
Definition jobcommon.h:289
static GAIA_NODISCARD constexpr bool stores_inline()
Checks whether a callable of type F is stored inline, without a heap allocation.
Definition jobcommon.h:224
void operator()() const
Invokes the stored callable.
Definition jobcommon.h:302
Process-wide worker pool for dependent frame and background jobs.
Definition threadpool.h:78
Definition jobcommon.h:18
static void free(void *ptr)
Releases storage allocated through an adaptor.
Definition mem_alloc.h:276
Allocation metadata used when reserving a job slot.
Definition jobcommon.h:45
JobPriority priority
Priority encoded into the allocated job handle.
Definition jobcommon.h:47
Half-open item range passed to a parallel job callback.
Definition jobcommon.h:51
uint32_t idxStart
First item index processed by this invocation.
Definition jobcommon.h:53
uint32_t idxEnd
One-past-the-last item index processed by this invocation.
Definition jobcommon.h:55
Non-owning callback descriptor for parallel jobs.
Definition jobcommon.h:363
void(* invoke)(void *, const JobArgs &)
Function that invokes the callback stored in the context.
Definition jobcommon.h:367
void * pCtx
Non-owning callback context.
Definition jobcommon.h:365
JobPriority priority
Queue priority used for each range job.
Definition jobcommon.h:369
Callable and priority for a range-partitioned parallel job.
Definition jobcommon.h:354
JobPriority priority
Queue priority used for each range job.
Definition jobcommon.h:358
JobArgsFunc func
Callable invoked once for each scheduled range.
Definition jobcommon.h:356
Callable and scheduling options for a single job.
Definition jobcommon.h:340
JobFunc func
Callable executed by the worker. The job owns it until the job is deleted. An automatically deleted j...
Definition jobcommon.h:346
JobCreationFlags flags
Creation and lifetime options.
Definition jobcommon.h:350
JobPriority priority
Queue priority used to schedule the callable.
Definition jobcommon.h:348
Per-thread execution state owned by ThreadPool.
Definition jobcommon.h:375
bool background
True when the worker executes background jobs.
Definition jobcommon.h:383
JobPriority prio
Job priority.
Definition jobcommon.h:381
uint32_t workerIdx
Worker index.
Definition jobcommon.h:379
Event event
Event signaled when a job is executed.
Definition jobcommon.h:387
ThreadPool * tp
Thread pool pointer.
Definition jobcommon.h:377
bool threadCreated
True when the worker thread has been successfully created.
Definition jobcommon.h:385
JobQueue< 512 > jobQueue
Lock-free work stealing queue for the jobs.
Definition jobcommon.h:389