The open source OpenXR runtime

u/worker: Add task worker helper

authored by

Jakob Bornecrantz and committed by
Moses Turner
771aeda7 76d89447

+925
+4
src/xrt/auxiliary/CMakeLists.txt
··· 214 214 util/u_verify.h 215 215 util/u_process.c 216 216 util/u_process.h 217 + util/u_worker.c 218 + util/u_worker.cpp 219 + util/u_worker.h 220 + util/u_worker.hpp 217 221 "${CMAKE_CURRENT_BINARY_DIR}/u_git_tag.c" 218 222 ) 219 223
+4
src/xrt/auxiliary/meson.build
··· 87 87 'util/u_verify.h', 88 88 'util/u_process.c', 89 89 'util/u_process.h', 90 + 'util/u_worker.c', 91 + 'util/u_worker.cpp', 92 + 'util/u_worker.h', 93 + 'util/u_worker.hpp', 90 94 ) + [ 91 95 u_git_tag_c, 92 96 ],
+534
src/xrt/auxiliary/util/u_worker.c
··· 1 + // Copyright 2022, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Simple worker pool. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * 8 + * @ingroup aux_util 9 + */ 10 + 11 + #include "os/os_threading.h" 12 + 13 + #include "util/u_logging.h" 14 + #include "util/u_worker.h" 15 + #include "util/u_trace_marker.h" 16 + 17 + 18 + #define MAX_TASK_COUNT (64) 19 + #define MAX_THREAD_COUNT (16) 20 + 21 + struct group; 22 + struct pool; 23 + 24 + struct task 25 + { 26 + //! Group this task was submitted from. 27 + struct group *g; 28 + 29 + //! Function. 30 + u_worker_group_func_t func; 31 + 32 + //! Function data. 33 + void *data; 34 + }; 35 + 36 + struct thread 37 + { 38 + //! Pool this thread belongs to. 39 + struct pool *p; 40 + 41 + // Native thread. 42 + struct os_thread thread; 43 + }; 44 + 45 + struct pool 46 + { 47 + struct u_worker_thread_pool base; 48 + 49 + //! Big contenious mutex. 50 + struct os_mutex mutex; 51 + 52 + //! Array of tasks. 53 + struct task tasks[MAX_TASK_COUNT]; 54 + 55 + //! Number of tasks in array. 56 + size_t tasks_in_array_count; 57 + 58 + struct 59 + { 60 + size_t count; 61 + struct os_cond cond; 62 + } available; //!< For worker threads. 63 + 64 + //! Given at creation. 65 + uint32_t initial_worker_limit; 66 + 67 + //! Currently the number of works that can work, waiting increases this. 68 + uint32_t worker_limit; 69 + 70 + //! Number of threads working on tasks. 71 + size_t working_count; 72 + 73 + //! Number of created threads. 74 + size_t thread_count; 75 + 76 + //! The worker threads. 77 + struct thread threads[MAX_THREAD_COUNT]; 78 + 79 + //! Is the pool up and running? 80 + bool running; 81 + }; 82 + 83 + struct group 84 + { 85 + //! Base struct has to come first. 86 + struct u_worker_group base; 87 + 88 + //! Pointer to poll of threads. 89 + struct u_worker_thread_pool *uwtp; 90 + 91 + //! Number of tasks that is pending or being worked on in this group. 92 + size_t current_submitted_tasks_count; 93 + 94 + //! Number of threads that have been released or newly entered wait. 95 + size_t released_count; 96 + 97 + struct 98 + { 99 + size_t count; 100 + struct os_cond cond; 101 + } waiting; //!< For wait_all 102 + }; 103 + 104 + 105 + /* 106 + * 107 + * Helper functions. 108 + * 109 + */ 110 + 111 + static inline struct group * 112 + group(struct u_worker_group *uwp) 113 + { 114 + return (struct group *)uwp; 115 + } 116 + 117 + static inline struct pool * 118 + pool(struct u_worker_thread_pool *uwtp) 119 + { 120 + return (struct pool *)uwtp; 121 + } 122 + 123 + 124 + /* 125 + * 126 + * Internal pool functions. 127 + * 128 + */ 129 + 130 + static void 131 + locked_pool_pop_task(struct pool *p, struct task *out_task) 132 + { 133 + assert(p->tasks_in_array_count > 0); 134 + 135 + for (size_t i = 0; i < MAX_TASK_COUNT; i++) { 136 + if (p->tasks[i].func == NULL) { 137 + continue; 138 + } 139 + 140 + *out_task = p->tasks[i]; 141 + p->tasks[i] = (struct task){NULL, NULL, NULL}; 142 + p->tasks_in_array_count--; 143 + return; 144 + } 145 + 146 + assert(false); 147 + } 148 + 149 + static void 150 + locked_pool_push_task(struct pool *p, struct group *g, u_worker_group_func_t func, void *data) 151 + { 152 + assert(p->tasks_in_array_count < MAX_TASK_COUNT); 153 + 154 + for (size_t i = 0; i < MAX_TASK_COUNT; i++) { 155 + if (p->tasks[i].func != NULL) { 156 + continue; 157 + } 158 + 159 + p->tasks[i] = (struct task){g, func, data}; 160 + p->tasks_in_array_count++; 161 + g->current_submitted_tasks_count++; 162 + return; 163 + } 164 + 165 + assert(false); 166 + } 167 + 168 + static void 169 + locked_pool_wake_worker_if_allowed(struct pool *p) 170 + { 171 + // No tasks in array, don't wake any thread. 172 + if (p->tasks_in_array_count == 0) { 173 + return; 174 + } 175 + 176 + // The number of working threads is at the limit. 177 + if (p->working_count >= p->worker_limit) { 178 + return; 179 + } 180 + 181 + // No waiting thread. 182 + if (p->available.count == 0) { 183 + //! @todo Is this a error? 184 + return; 185 + } 186 + 187 + os_cond_signal(&p->available.cond); 188 + } 189 + 190 + 191 + /* 192 + * 193 + * Thread group functions. 194 + * 195 + */ 196 + 197 + static bool 198 + locked_group_should_enter_wait_loop(struct pool *p, struct group *g) 199 + { 200 + if (g->current_submitted_tasks_count == 0) { 201 + return false; 202 + } 203 + 204 + // Enter the loop as a released thread. 205 + g->released_count++; 206 + 207 + return true; 208 + } 209 + 210 + static bool 211 + locked_group_should_wait(struct pool *p, struct group *g) 212 + { 213 + /* 214 + * There are several cases that needs to be covered by this function. 215 + * 216 + * A thread is entering the wait_all function for the first time, and 217 + * work is outstanding what we should do then is increase the worker 218 + * limit and wait on the conditional. 219 + * 220 + * Similar to above, we where woken up, there are more work outstanding 221 + * on the group and we had been released, remove one released and up the 222 + * worker limit, then wait on the conditional. 223 + * 224 + * A thread (or more) has been woken up and no new tasks has been 225 + * submitted, then break out of the loop and decrement the released 226 + * count. 227 + * 228 + * As above, but we where one of many woken up but only one thread had 229 + * been released and that released count had been taken, then we should 230 + * do nothing and wait again. 231 + */ 232 + 233 + // Tasks available. 234 + if (g->current_submitted_tasks_count > 0) { 235 + 236 + // We have been released or newly entered the loop. 237 + if (g->released_count > 0) { 238 + g->released_count--; 239 + p->worker_limit++; 240 + 241 + // Wake a worker with the new worker limit. 242 + locked_pool_wake_worker_if_allowed(p); 243 + } 244 + 245 + return true; 246 + } 247 + 248 + // No tasks, and we have been released, party! 249 + if (g->released_count > 0) { 250 + g->released_count--; 251 + return false; 252 + } 253 + 254 + // We where woken up, but nothing had been released, loop again. 255 + return true; 256 + } 257 + 258 + static void 259 + locked_group_wake_waiter_if_allowed(struct pool *p, struct group *g) 260 + { 261 + // Are there still outstanding tasks? 262 + if (g->current_submitted_tasks_count > 0) { 263 + return; 264 + } 265 + 266 + // Is there a thread waiting or not? 267 + if (g->waiting.count == 0) { 268 + return; 269 + } 270 + 271 + // Wake one waiting thread. 272 + os_cond_signal(&g->waiting.cond); 273 + 274 + assert(p->worker_limit > p->initial_worker_limit); 275 + 276 + // Remove one waiting threads. 277 + p->worker_limit--; 278 + 279 + // We have released one thread. 280 + g->released_count++; 281 + } 282 + 283 + static void 284 + locked_group_wait(struct pool *p, struct group *g) 285 + { 286 + // Update tracking. 287 + g->waiting.count++; 288 + 289 + // The wait, also unlocks the mutex. 290 + os_cond_wait(&g->waiting.cond, &p->mutex); 291 + 292 + // Update tracking. 293 + g->waiting.count--; 294 + } 295 + 296 + 297 + /* 298 + * 299 + * Thread internal functions. 300 + * 301 + */ 302 + 303 + static bool 304 + locked_thread_allowed_to_work(struct pool *p) 305 + { 306 + // No work for you! 307 + if (p->tasks_in_array_count == 0) { 308 + return false; 309 + } 310 + 311 + // Reached the limit. 312 + if (p->working_count >= p->worker_limit) { 313 + return false; 314 + } 315 + 316 + return true; 317 + } 318 + 319 + static void 320 + locked_thread_wait_for_work(struct pool *p) 321 + { 322 + // Update tracking. 323 + p->available.count++; 324 + 325 + // The wait, also unlocks the mutex. 326 + os_cond_wait(&p->available.cond, &p->mutex); 327 + 328 + // Update tracking. 329 + p->available.count--; 330 + } 331 + 332 + static void * 333 + run_func(void *ptr) 334 + { 335 + struct thread *t = (struct thread *)ptr; 336 + struct pool *p = t->p; 337 + 338 + os_mutex_lock(&p->mutex); 339 + 340 + while (p->running) { 341 + 342 + if (!locked_thread_allowed_to_work(p)) { 343 + locked_thread_wait_for_work(p); 344 + 345 + // Check running first when woken up. 346 + continue; 347 + } 348 + 349 + // Pop a task from the pool. 350 + struct task task = {NULL, NULL, NULL}; 351 + locked_pool_pop_task(p, &task); 352 + 353 + // We are now counting as working, needed for wake below. 354 + p->working_count++; 355 + 356 + // Signal another thread if conditions are met. 357 + locked_pool_wake_worker_if_allowed(p); 358 + 359 + // Do the actual work here. 360 + os_mutex_unlock(&p->mutex); 361 + task.func(task.data); 362 + os_mutex_lock(&p->mutex); 363 + 364 + // No longer working. 365 + p->working_count--; 366 + 367 + // Only now decrement the task count on the owning group. 368 + task.g->current_submitted_tasks_count--; 369 + 370 + // Wake up any waiter. 371 + locked_group_wake_waiter_if_allowed(p, task.g); 372 + } 373 + 374 + // Make sure all threads are woken up. 375 + os_cond_signal(&p->available.cond); 376 + 377 + os_mutex_unlock(&p->mutex); 378 + 379 + return NULL; 380 + } 381 + 382 + 383 + /* 384 + * 385 + * 'Exported' thread pool functions. 386 + * 387 + */ 388 + 389 + struct u_worker_thread_pool * 390 + u_worker_thread_pool_create(uint32_t starting_worker_count, uint32_t thread_count) 391 + { 392 + XRT_TRACE_MARKER(); 393 + 394 + assert(starting_worker_count < thread_count); 395 + if (starting_worker_count >= thread_count) { 396 + return NULL; 397 + } 398 + 399 + assert(thread_count <= MAX_THREAD_COUNT); 400 + if (thread_count > MAX_THREAD_COUNT) { 401 + return NULL; 402 + } 403 + 404 + struct pool *p = U_TYPED_CALLOC(struct pool); 405 + p->base.reference.count = 1; 406 + p->initial_worker_limit = starting_worker_count; 407 + p->worker_limit = starting_worker_count; 408 + p->thread_count = thread_count; 409 + p->running = true; 410 + 411 + for (size_t i = 0; i < thread_count; i++) { 412 + p->threads[i].p = p; 413 + os_thread_init(&p->threads[i].thread); 414 + os_thread_start(&p->threads[i].thread, run_func, &p->threads[i]); 415 + } 416 + 417 + return (struct u_worker_thread_pool *)p; 418 + } 419 + 420 + void 421 + u_worker_thread_pool_destroy(struct u_worker_thread_pool *uwtp) 422 + { 423 + XRT_TRACE_MARKER(); 424 + 425 + struct pool *p = pool(uwtp); 426 + 427 + os_mutex_lock(&p->mutex); 428 + 429 + p->running = false; 430 + os_cond_signal(&p->available.cond); 431 + os_mutex_unlock(&p->mutex); 432 + 433 + // Wait for all threads. 434 + for (size_t i = 0; i < p->thread_count; i++) { 435 + os_thread_join(&p->threads[i].thread); 436 + os_thread_destroy(&p->threads[i].thread); 437 + } 438 + 439 + os_mutex_destroy(&p->mutex); 440 + os_cond_destroy(&p->available.cond); 441 + 442 + free(p); 443 + } 444 + 445 + 446 + /* 447 + * 448 + * 'Exported' group functions. 449 + * 450 + */ 451 + 452 + struct u_worker_group * 453 + u_worker_group_create(struct u_worker_thread_pool *uwtp) 454 + { 455 + XRT_TRACE_MARKER(); 456 + 457 + struct group *g = U_TYPED_CALLOC(struct group); 458 + g->base.reference.count = 1; 459 + u_worker_thread_pool_reference(&g->uwtp, uwtp); 460 + 461 + os_cond_init(&g->waiting.cond); 462 + 463 + return (struct u_worker_group *)g; 464 + } 465 + 466 + void 467 + u_worker_group_push(struct u_worker_group *uwp, u_worker_group_func_t f, void *data) 468 + { 469 + XRT_TRACE_MARKER(); 470 + 471 + struct group *g = group(uwp); 472 + struct pool *p = pool(g->uwtp); 473 + 474 + os_mutex_lock(&p->mutex); 475 + while (p->tasks_in_array_count >= MAX_TASK_COUNT) { 476 + os_mutex_unlock(&p->mutex); 477 + 478 + //! @todo Don't wait all, wait one. 479 + u_worker_group_wait_all(uwp); 480 + 481 + os_mutex_lock(&p->mutex); 482 + } 483 + 484 + locked_pool_push_task(p, g, f, data); 485 + 486 + // There are worker threads available, wake one up. 487 + if (p->available.count > 0) { 488 + os_cond_signal(&p->available.cond); 489 + } 490 + 491 + os_mutex_unlock(&p->mutex); 492 + } 493 + 494 + void 495 + u_worker_group_wait_all(struct u_worker_group *uwp) 496 + { 497 + XRT_TRACE_MARKER(); 498 + 499 + struct group *g = group(uwp); 500 + struct pool *p = pool(g->uwtp); 501 + 502 + os_mutex_lock(&p->mutex); 503 + 504 + // Can we early out? 505 + if (!locked_group_should_enter_wait_loop(p, g)) { 506 + os_mutex_unlock(&p->mutex); 507 + return; 508 + } 509 + 510 + // Wait here until all work been started and completed. 511 + while (locked_group_should_wait(p, g)) { 512 + // Do the wait. 513 + locked_group_wait(p, g); 514 + } 515 + 516 + os_mutex_unlock(&p->mutex); 517 + } 518 + 519 + void 520 + u_worker_group_destroy(struct u_worker_group *uwp) 521 + { 522 + XRT_TRACE_MARKER(); 523 + 524 + struct group *g = group(uwp); 525 + assert(g->base.reference.count == 0); 526 + 527 + u_worker_group_wait_all(uwp); 528 + 529 + u_worker_thread_pool_reference(&g->uwtp, NULL); 530 + 531 + os_cond_destroy(&g->waiting.cond); 532 + 533 + free(uwp); 534 + }
+20
src/xrt/auxiliary/util/u_worker.cpp
··· 1 + // Copyright 2022, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief C++ wrappers for workers. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * 8 + * @ingroup aux_util 9 + */ 10 + 11 + #include "util/u_worker.hpp" 12 + 13 + 14 + void 15 + xrt::auxiliary::util::TaskCollection::cCallback(void *data_ptr) 16 + { 17 + auto &f = *static_cast<Functor *>(data_ptr); 18 + f(); 19 + f = nullptr; 20 + }
+172
src/xrt/auxiliary/util/u_worker.h
··· 1 + // Copyright 2022, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief Worker and threading pool. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * 8 + * @ingroup aux_util 9 + */ 10 + 11 + #include "xrt/xrt_defines.h" 12 + 13 + 14 + #ifdef __cplusplus 15 + extern "C" { 16 + #endif 17 + 18 + 19 + /* 20 + * 21 + * Worker thread pool. 22 + * 23 + */ 24 + 25 + /*! 26 + * A worker pool, can shared between multiple groups worker pool. 27 + * 28 + * @ingroup aux_util 29 + */ 30 + struct u_worker_thread_pool 31 + { 32 + struct xrt_reference reference; 33 + }; 34 + 35 + /*! 36 + * Creates a new thread pool to be used by a worker group. 37 + * 38 + * @param starting_worker_count How many worker threads can be active at the 39 + * same time without any "donated" threads. 40 + * @param thread_count The number of threads to be created in total, 41 + * this is the maximum threads that can be in 42 + * flight at the same time. 43 + * 44 + * @ingroup aux_util 45 + */ 46 + struct u_worker_thread_pool * 47 + u_worker_thread_pool_create(uint32_t starting_worker_count, uint32_t thread_count); 48 + 49 + /*! 50 + * Internal function, only called by reference. 51 + * 52 + * @ingroup aux_util 53 + */ 54 + void 55 + u_worker_thread_pool_destroy(struct u_worker_thread_pool *uwtp); 56 + 57 + /*! 58 + * Standard Monado reference function. 59 + * 60 + * @ingroup aux_util 61 + */ 62 + static inline void 63 + u_worker_thread_pool_reference(struct u_worker_thread_pool **dst, struct u_worker_thread_pool *src) 64 + { 65 + struct u_worker_thread_pool *old_dst = *dst; 66 + 67 + if (old_dst == src) { 68 + return; 69 + } 70 + 71 + if (src) { 72 + xrt_reference_inc(&src->reference); 73 + } 74 + 75 + *dst = src; 76 + 77 + if (old_dst) { 78 + if (xrt_reference_dec(&old_dst->reference)) { 79 + u_worker_thread_pool_destroy(old_dst); 80 + } 81 + } 82 + } 83 + 84 + 85 + /* 86 + * 87 + * Worker group. 88 + * 89 + */ 90 + 91 + /*! 92 + * A worker group where you submit tasks to. Can share a thread pool with 93 + * multiple groups. Also can "donate" a thread to the thread pool by waiting. 94 + * 95 + * @ingroup aux_util 96 + */ 97 + struct u_worker_group 98 + { 99 + struct xrt_reference reference; 100 + }; 101 + 102 + /*! 103 + * Function typedef for tasks. 104 + * 105 + * @ingroup aux_util 106 + */ 107 + typedef void (*u_worker_group_func_t)(void *); 108 + 109 + /*! 110 + * Create a new worker group. 111 + * 112 + * @ingroup aux_util 113 + */ 114 + struct u_worker_group * 115 + u_worker_group_create(struct u_worker_thread_pool *uwtp); 116 + 117 + /*! 118 + * Push a new task to worker group. 119 + * 120 + * @ingroup aux_util 121 + */ 122 + void 123 + u_worker_group_push(struct u_worker_group *uwg, u_worker_group_func_t f, void *data); 124 + 125 + /*! 126 + * Wait for all pushed tasks to be completed, "donates" this thread to the 127 + * shared thread pool. 128 + * 129 + * @ingroup aux_util 130 + */ 131 + void 132 + u_worker_group_wait_all(struct u_worker_group *uwg); 133 + 134 + /*! 135 + * Destroy a worker pool. 136 + * 137 + * @ingroup aux_util 138 + */ 139 + void 140 + u_worker_group_destroy(struct u_worker_group *uwg); 141 + 142 + /*! 143 + * Standard Monado reference function. 144 + * 145 + * @ingroup aux_util 146 + */ 147 + static inline void 148 + u_worker_group_reference(struct u_worker_group **dst, struct u_worker_group *src) 149 + { 150 + struct u_worker_group *old_dst = *dst; 151 + 152 + if (old_dst == src) { 153 + return; 154 + } 155 + 156 + if (src) { 157 + xrt_reference_inc(&src->reference); 158 + } 159 + 160 + *dst = src; 161 + 162 + if (old_dst) { 163 + if (xrt_reference_dec(&old_dst->reference)) { 164 + u_worker_group_destroy(old_dst); 165 + } 166 + } 167 + } 168 + 169 + 170 + #ifdef __cplusplus 171 + } 172 + #endif
+191
src/xrt/auxiliary/util/u_worker.hpp
··· 1 + // Copyright 2022, Collabora, Ltd. 2 + // SPDX-License-Identifier: BSL-1.0 3 + /*! 4 + * @file 5 + * @brief C++ wrappers for workers. 6 + * @author Jakob Bornecrantz <jakob@collabora.com> 7 + * 8 + * @ingroup aux_util 9 + */ 10 + 11 + #pragma once 12 + 13 + #include "util/u_worker.h" 14 + 15 + #include <vector> 16 + #include <cassert> 17 + #include <functional> 18 + 19 + 20 + namespace xrt::auxiliary::util { 21 + 22 + class TaskCollection; 23 + class SharedThreadGroup; 24 + 25 + /*! 26 + * Wrapper around @ref u_worker_thread_pool. 27 + * 28 + * @ingroup aux_util 29 + */ 30 + class SharedThreadPool 31 + { 32 + private: 33 + u_worker_thread_pool *mPool = nullptr; 34 + 35 + 36 + public: 37 + explicit SharedThreadPool(SharedThreadPool const &copy) 38 + { 39 + u_worker_thread_pool_reference(&mPool, copy.mPool); 40 + } 41 + 42 + /*! 43 + * Take a C thread pool as argument in case the pool is shared between 44 + * different C++ components over C interfaces, or created externally. 45 + */ 46 + explicit SharedThreadPool(u_worker_thread_pool *uwtp) 47 + { 48 + u_worker_thread_pool_reference(&mPool, uwtp); 49 + } 50 + 51 + /*! 52 + * @copydoc u_worker_thread_pool_create. 53 + */ 54 + explicit SharedThreadPool(uint32_t starting_worker_count, uint32_t thread_count) 55 + { 56 + mPool = u_worker_thread_pool_create(starting_worker_count, thread_count); 57 + } 58 + 59 + ~SharedThreadPool() 60 + { 61 + u_worker_thread_pool_reference(&mPool, nullptr); 62 + } 63 + 64 + SharedThreadPool & 65 + operator=(const SharedThreadPool &other) 66 + { 67 + if (this == &other) { 68 + return *this; 69 + } 70 + 71 + u_worker_thread_pool_reference(&mPool, other.mPool); 72 + return *this; 73 + } 74 + 75 + friend SharedThreadGroup; 76 + 77 + // No default contstructor. 78 + SharedThreadPool() = delete; 79 + // No move. 80 + SharedThreadPool(SharedThreadPool &&) = delete; 81 + // No move assign. 82 + SharedThreadPool & 83 + operator=(SharedThreadPool &&) = delete; 84 + }; 85 + 86 + /*! 87 + * Wrapper around @ref u_worker_group, use @ref TaskCollection to dispatch work. 88 + * 89 + * @ingroup aux_util 90 + */ 91 + class SharedThreadGroup 92 + { 93 + private: 94 + u_worker_group *mGroup = nullptr; 95 + 96 + 97 + public: 98 + explicit SharedThreadGroup(SharedThreadPool const &stp) 99 + { 100 + mGroup = u_worker_group_create(stp.mPool); 101 + } 102 + 103 + ~SharedThreadGroup() 104 + { 105 + u_worker_group_reference(&mGroup, nullptr); 106 + } 107 + 108 + friend TaskCollection; 109 + 110 + // No default constructor. 111 + SharedThreadGroup() = delete; 112 + // Do not move or copy the shared thread group. 113 + SharedThreadGroup(SharedThreadGroup const &) = delete; 114 + SharedThreadGroup(SharedThreadGroup &&) = delete; 115 + SharedThreadGroup & 116 + operator=(SharedThreadGroup const &) = delete; 117 + SharedThreadGroup & 118 + operator=(SharedThreadGroup &&) = delete; 119 + }; 120 + 121 + /*! 122 + * Class to let users fall into a pit of success by 123 + * being designed as a one shot dispatcher instance. 124 + * 125 + * @ingroup aux_util 126 + */ 127 + class TaskCollection 128 + { 129 + public: 130 + typedef std::function<void()> Functor; 131 + 132 + 133 + private: 134 + static constexpr size_t kSize = 16; 135 + 136 + Functor mFunctors[kSize] = {}; 137 + u_worker_group *mGroup = nullptr; 138 + 139 + 140 + public: 141 + /*! 142 + * Give all Functors when constructed, some what partially 143 + * avoids use after leaving scope issues of function delegates. 144 + */ 145 + TaskCollection(SharedThreadGroup const &stc, std::vector<Functor> funcs) 146 + { 147 + assert(funcs.size() <= kSize); 148 + 149 + u_worker_group_reference(&mGroup, stc.mGroup); 150 + 151 + for (size_t i = 0; i < kSize && i < funcs.size(); i++) { 152 + mFunctors[i] = funcs[i]; 153 + u_worker_group_push(mGroup, &cCallback, &mFunctors[i]); 154 + } 155 + } 156 + 157 + ~TaskCollection() 158 + { 159 + // Also unreferences the group. 160 + waitAll(); 161 + } 162 + 163 + /*! 164 + * Waits for all given tasks to complete, also frees the group. 165 + */ 166 + void 167 + waitAll() 168 + { 169 + if (mGroup == nullptr) { 170 + return; 171 + } 172 + u_worker_group_wait_all(mGroup); 173 + u_worker_group_reference(&mGroup, nullptr); 174 + } 175 + 176 + 177 + // Do not move or copy the task collection. 178 + TaskCollection(TaskCollection const &) = delete; 179 + TaskCollection(TaskCollection &&) = delete; 180 + TaskCollection & 181 + operator=(TaskCollection const &) = delete; 182 + TaskCollection & 183 + operator=(TaskCollection &&) = delete; 184 + 185 + 186 + private: 187 + static void 188 + cCallback(void *data_ptr); 189 + }; 190 + 191 + } // namespace xrt::auxiliary::util