1/**************************************************************************/
2/* command_queue_mt.h */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#ifndef COMMAND_QUEUE_MT_H
32#define COMMAND_QUEUE_MT_H
33
34#include "core/os/memory.h"
35#include "core/os/mutex.h"
36#include "core/os/semaphore.h"
37#include "core/string/print_string.h"
38#include "core/templates/local_vector.h"
39#include "core/templates/simple_type.h"
40#include "core/typedefs.h"
41
42#define COMMA(N) _COMMA_##N
43#define _COMMA_0
44#define _COMMA_1 ,
45#define _COMMA_2 ,
46#define _COMMA_3 ,
47#define _COMMA_4 ,
48#define _COMMA_5 ,
49#define _COMMA_6 ,
50#define _COMMA_7 ,
51#define _COMMA_8 ,
52#define _COMMA_9 ,
53#define _COMMA_10 ,
54#define _COMMA_11 ,
55#define _COMMA_12 ,
56#define _COMMA_13 ,
57#define _COMMA_14 ,
58#define _COMMA_15 ,
59
60// 1-based comma separated list of ITEMs
61#define COMMA_SEP_LIST(ITEM, LENGTH) _COMMA_SEP_LIST_##LENGTH(ITEM)
62#define _COMMA_SEP_LIST_15(ITEM) \
63 _COMMA_SEP_LIST_14(ITEM) \
64 , ITEM(15)
65#define _COMMA_SEP_LIST_14(ITEM) \
66 _COMMA_SEP_LIST_13(ITEM) \
67 , ITEM(14)
68#define _COMMA_SEP_LIST_13(ITEM) \
69 _COMMA_SEP_LIST_12(ITEM) \
70 , ITEM(13)
71#define _COMMA_SEP_LIST_12(ITEM) \
72 _COMMA_SEP_LIST_11(ITEM) \
73 , ITEM(12)
74#define _COMMA_SEP_LIST_11(ITEM) \
75 _COMMA_SEP_LIST_10(ITEM) \
76 , ITEM(11)
77#define _COMMA_SEP_LIST_10(ITEM) \
78 _COMMA_SEP_LIST_9(ITEM) \
79 , ITEM(10)
80#define _COMMA_SEP_LIST_9(ITEM) \
81 _COMMA_SEP_LIST_8(ITEM) \
82 , ITEM(9)
83#define _COMMA_SEP_LIST_8(ITEM) \
84 _COMMA_SEP_LIST_7(ITEM) \
85 , ITEM(8)
86#define _COMMA_SEP_LIST_7(ITEM) \
87 _COMMA_SEP_LIST_6(ITEM) \
88 , ITEM(7)
89#define _COMMA_SEP_LIST_6(ITEM) \
90 _COMMA_SEP_LIST_5(ITEM) \
91 , ITEM(6)
92#define _COMMA_SEP_LIST_5(ITEM) \
93 _COMMA_SEP_LIST_4(ITEM) \
94 , ITEM(5)
95#define _COMMA_SEP_LIST_4(ITEM) \
96 _COMMA_SEP_LIST_3(ITEM) \
97 , ITEM(4)
98#define _COMMA_SEP_LIST_3(ITEM) \
99 _COMMA_SEP_LIST_2(ITEM) \
100 , ITEM(3)
101#define _COMMA_SEP_LIST_2(ITEM) \
102 _COMMA_SEP_LIST_1(ITEM) \
103 , ITEM(2)
104#define _COMMA_SEP_LIST_1(ITEM) \
105 _COMMA_SEP_LIST_0(ITEM) \
106 ITEM(1)
107#define _COMMA_SEP_LIST_0(ITEM)
108
109// 1-based semicolon separated list of ITEMs
110#define SEMIC_SEP_LIST(ITEM, LENGTH) _SEMIC_SEP_LIST_##LENGTH(ITEM)
111#define _SEMIC_SEP_LIST_15(ITEM) \
112 _SEMIC_SEP_LIST_14(ITEM); \
113 ITEM(15)
114#define _SEMIC_SEP_LIST_14(ITEM) \
115 _SEMIC_SEP_LIST_13(ITEM); \
116 ITEM(14)
117#define _SEMIC_SEP_LIST_13(ITEM) \
118 _SEMIC_SEP_LIST_12(ITEM); \
119 ITEM(13)
120#define _SEMIC_SEP_LIST_12(ITEM) \
121 _SEMIC_SEP_LIST_11(ITEM); \
122 ITEM(12)
123#define _SEMIC_SEP_LIST_11(ITEM) \
124 _SEMIC_SEP_LIST_10(ITEM); \
125 ITEM(11)
126#define _SEMIC_SEP_LIST_10(ITEM) \
127 _SEMIC_SEP_LIST_9(ITEM); \
128 ITEM(10)
129#define _SEMIC_SEP_LIST_9(ITEM) \
130 _SEMIC_SEP_LIST_8(ITEM); \
131 ITEM(9)
132#define _SEMIC_SEP_LIST_8(ITEM) \
133 _SEMIC_SEP_LIST_7(ITEM); \
134 ITEM(8)
135#define _SEMIC_SEP_LIST_7(ITEM) \
136 _SEMIC_SEP_LIST_6(ITEM); \
137 ITEM(7)
138#define _SEMIC_SEP_LIST_6(ITEM) \
139 _SEMIC_SEP_LIST_5(ITEM); \
140 ITEM(6)
141#define _SEMIC_SEP_LIST_5(ITEM) \
142 _SEMIC_SEP_LIST_4(ITEM); \
143 ITEM(5)
144#define _SEMIC_SEP_LIST_4(ITEM) \
145 _SEMIC_SEP_LIST_3(ITEM); \
146 ITEM(4)
147#define _SEMIC_SEP_LIST_3(ITEM) \
148 _SEMIC_SEP_LIST_2(ITEM); \
149 ITEM(3)
150#define _SEMIC_SEP_LIST_2(ITEM) \
151 _SEMIC_SEP_LIST_1(ITEM); \
152 ITEM(2)
153#define _SEMIC_SEP_LIST_1(ITEM) \
154 _SEMIC_SEP_LIST_0(ITEM) \
155 ITEM(1)
156#define _SEMIC_SEP_LIST_0(ITEM)
157
158// 1-based space separated list of ITEMs
159#define SPACE_SEP_LIST(ITEM, LENGTH) _SPACE_SEP_LIST_##LENGTH(ITEM)
160#define _SPACE_SEP_LIST_15(ITEM) \
161 _SPACE_SEP_LIST_14(ITEM) \
162 ITEM(15)
163#define _SPACE_SEP_LIST_14(ITEM) \
164 _SPACE_SEP_LIST_13(ITEM) \
165 ITEM(14)
166#define _SPACE_SEP_LIST_13(ITEM) \
167 _SPACE_SEP_LIST_12(ITEM) \
168 ITEM(13)
169#define _SPACE_SEP_LIST_12(ITEM) \
170 _SPACE_SEP_LIST_11(ITEM) \
171 ITEM(12)
172#define _SPACE_SEP_LIST_11(ITEM) \
173 _SPACE_SEP_LIST_10(ITEM) \
174 ITEM(11)
175#define _SPACE_SEP_LIST_10(ITEM) \
176 _SPACE_SEP_LIST_9(ITEM) \
177 ITEM(10)
178#define _SPACE_SEP_LIST_9(ITEM) \
179 _SPACE_SEP_LIST_8(ITEM) \
180 ITEM(9)
181#define _SPACE_SEP_LIST_8(ITEM) \
182 _SPACE_SEP_LIST_7(ITEM) \
183 ITEM(8)
184#define _SPACE_SEP_LIST_7(ITEM) \
185 _SPACE_SEP_LIST_6(ITEM) \
186 ITEM(7)
187#define _SPACE_SEP_LIST_6(ITEM) \
188 _SPACE_SEP_LIST_5(ITEM) \
189 ITEM(6)
190#define _SPACE_SEP_LIST_5(ITEM) \
191 _SPACE_SEP_LIST_4(ITEM) \
192 ITEM(5)
193#define _SPACE_SEP_LIST_4(ITEM) \
194 _SPACE_SEP_LIST_3(ITEM) \
195 ITEM(4)
196#define _SPACE_SEP_LIST_3(ITEM) \
197 _SPACE_SEP_LIST_2(ITEM) \
198 ITEM(3)
199#define _SPACE_SEP_LIST_2(ITEM) \
200 _SPACE_SEP_LIST_1(ITEM) \
201 ITEM(2)
202#define _SPACE_SEP_LIST_1(ITEM) \
203 _SPACE_SEP_LIST_0(ITEM) \
204 ITEM(1)
205#define _SPACE_SEP_LIST_0(ITEM)
206
207#define ARG(N) p##N
208#define PARAM(N) P##N p##N
209#define TYPE_PARAM(N) class P##N
210#define PARAM_DECL(N) typename GetSimpleTypeT<P##N>::type_t p##N
211
212#define DECL_CMD(N) \
213 template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
214 struct Command##N : public CommandBase { \
215 T *instance; \
216 M method; \
217 SEMIC_SEP_LIST(PARAM_DECL, N); \
218 virtual void call() override { \
219 (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
220 } \
221 };
222
223#define DECL_CMD_RET(N) \
224 template <class T, class M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) class R> \
225 struct CommandRet##N : public SyncCommand { \
226 R *ret; \
227 T *instance; \
228 M method; \
229 SEMIC_SEP_LIST(PARAM_DECL, N); \
230 virtual void call() override { \
231 *ret = (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
232 } \
233 };
234
235#define DECL_CMD_SYNC(N) \
236 template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
237 struct CommandSync##N : public SyncCommand { \
238 T *instance; \
239 M method; \
240 SEMIC_SEP_LIST(PARAM_DECL, N); \
241 virtual void call() override { \
242 (instance->*method)(COMMA_SEP_LIST(ARG, N)); \
243 } \
244 };
245
246#define TYPE_ARG(N) P##N
247#define CMD_TYPE(N) Command##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
248#define CMD_ASSIGN_PARAM(N) cmd->p##N = p##N
249
250#define DECL_PUSH(N) \
251 template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
252 void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
253 CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
254 cmd->instance = p_instance; \
255 cmd->method = p_method; \
256 SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
257 unlock(); \
258 if (sync) \
259 sync->post(); \
260 }
261
262#define CMD_RET_TYPE(N) CommandRet##N<T, M, COMMA_SEP_LIST(TYPE_ARG, N) COMMA(N) R>
263
264#define DECL_PUSH_AND_RET(N) \
265 template <class T, class M, COMMA_SEP_LIST(TYPE_PARAM, N) COMMA(N) class R> \
266 void push_and_ret(T *p_instance, M p_method, COMMA_SEP_LIST(PARAM, N) COMMA(N) R *r_ret) { \
267 SyncSemaphore *ss = _alloc_sync_sem(); \
268 CMD_RET_TYPE(N) *cmd = allocate_and_lock<CMD_RET_TYPE(N)>(); \
269 cmd->instance = p_instance; \
270 cmd->method = p_method; \
271 SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
272 cmd->ret = r_ret; \
273 cmd->sync_sem = ss; \
274 unlock(); \
275 if (sync) \
276 sync->post(); \
277 ss->sem.wait(); \
278 ss->in_use = false; \
279 }
280
281#define CMD_SYNC_TYPE(N) CommandSync##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
282
283#define DECL_PUSH_AND_SYNC(N) \
284 template <class T, class M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
285 void push_and_sync(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
286 SyncSemaphore *ss = _alloc_sync_sem(); \
287 CMD_SYNC_TYPE(N) *cmd = allocate_and_lock<CMD_SYNC_TYPE(N)>(); \
288 cmd->instance = p_instance; \
289 cmd->method = p_method; \
290 SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
291 cmd->sync_sem = ss; \
292 unlock(); \
293 if (sync) \
294 sync->post(); \
295 ss->sem.wait(); \
296 ss->in_use = false; \
297 }
298
299#define MAX_CMD_PARAMS 15
300
301class CommandQueueMT {
302 struct SyncSemaphore {
303 Semaphore sem;
304 bool in_use = false;
305 };
306
307 struct CommandBase {
308 virtual void call() = 0;
309 virtual void post() {}
310 virtual ~CommandBase() {}
311 };
312
313 struct SyncCommand : public CommandBase {
314 SyncSemaphore *sync_sem = nullptr;
315
316 virtual void post() override {
317 sync_sem->sem.post();
318 }
319 };
320
321 DECL_CMD(0)
322 SPACE_SEP_LIST(DECL_CMD, 15)
323
324 // Commands that return.
325 DECL_CMD_RET(0)
326 SPACE_SEP_LIST(DECL_CMD_RET, 15)
327
328 /* commands that don't return but sync */
329 DECL_CMD_SYNC(0)
330 SPACE_SEP_LIST(DECL_CMD_SYNC, 15)
331
332 /***** BASE *******/
333
334 enum {
335 DEFAULT_COMMAND_MEM_SIZE_KB = 256,
336 SYNC_SEMAPHORES = 8
337 };
338
339 LocalVector<uint8_t> command_mem;
340 SyncSemaphore sync_sems[SYNC_SEMAPHORES];
341 Mutex mutex;
342 Semaphore *sync = nullptr;
343
344 template <class T>
345 T *allocate() {
346 // alloc size is size+T+safeguard
347 uint32_t alloc_size = ((sizeof(T) + 8 - 1) & ~(8 - 1));
348 uint64_t size = command_mem.size();
349 command_mem.resize(size + alloc_size + 8);
350 *(uint64_t *)&command_mem[size] = alloc_size;
351 T *cmd = memnew_placement(&command_mem[size + 8], T);
352 return cmd;
353 }
354
355 template <class T>
356 T *allocate_and_lock() {
357 lock();
358 T *ret = allocate<T>();
359 return ret;
360 }
361
362 void _flush() {
363 lock();
364
365 uint64_t read_ptr = 0;
366 uint64_t limit = command_mem.size();
367
368 while (read_ptr < limit) {
369 uint64_t size = *(uint64_t *)&command_mem[read_ptr];
370 read_ptr += 8;
371 CommandBase *cmd = reinterpret_cast<CommandBase *>(&command_mem[read_ptr]);
372
373 cmd->call(); //execute the function
374 cmd->post(); //release in case it needs sync/ret
375 cmd->~CommandBase(); //should be done, so erase the command
376
377 read_ptr += size;
378 }
379
380 command_mem.clear();
381 unlock();
382 }
383
384 void lock();
385 void unlock();
386 void wait_for_flush();
387 SyncSemaphore *_alloc_sync_sem();
388
389public:
390 /* NORMAL PUSH COMMANDS */
391 DECL_PUSH(0)
392 SPACE_SEP_LIST(DECL_PUSH, 15)
393
394 /* PUSH AND RET COMMANDS */
395 DECL_PUSH_AND_RET(0)
396 SPACE_SEP_LIST(DECL_PUSH_AND_RET, 15)
397
398 /* PUSH AND RET SYNC COMMANDS*/
399 DECL_PUSH_AND_SYNC(0)
400 SPACE_SEP_LIST(DECL_PUSH_AND_SYNC, 15)
401
402 _FORCE_INLINE_ void flush_if_pending() {
403 if (unlikely(command_mem.size() > 0)) {
404 _flush();
405 }
406 }
407 void flush_all() {
408 _flush();
409 }
410
411 void wait_and_flush() {
412 ERR_FAIL_NULL(sync);
413 sync->wait();
414 _flush();
415 }
416
417 CommandQueueMT(bool p_sync);
418 ~CommandQueueMT();
419};
420
421#undef ARG
422#undef PARAM
423#undef TYPE_PARAM
424#undef PARAM_DECL
425#undef DECL_CMD
426#undef DECL_CMD_RET
427#undef DECL_CMD_SYNC
428#undef TYPE_ARG
429#undef CMD_TYPE
430#undef CMD_ASSIGN_PARAM
431#undef DECL_PUSH
432#undef CMD_RET_TYPE
433#undef DECL_PUSH_AND_RET
434#undef CMD_SYNC_TYPE
435#undef DECL_CMD_SYNC
436
437#endif // COMMAND_QUEUE_MT_H
438