3535#include " core/os/thread_safe.h"
3636#include " core/templates/command_queue_mt.h"
3737
38+ WorkerThreadPool::Task *const WorkerThreadPool::ThreadData::YIELDING = (Task *)1 ;
39+
3840void WorkerThreadPool::Task::free_template_userdata () {
3941 ERR_FAIL_NULL (template_userdata);
4042 ERR_FAIL_NULL (native_func_userdata);
@@ -60,11 +62,13 @@ void WorkerThreadPool::_process_task(Task *p_task) {
6062 // its pre-created threads can't have ScriptServer::thread_enter() called on them early.
6163 // Therefore, we do it late at the first opportunity, so in case the task
6264 // about to be run uses scripting, guarantees are held.
65+ task_mutex.lock ();
6366 if (!curr_thread.ready_for_scripting && ScriptServer::are_languages_initialized ()) {
67+ task_mutex.unlock ();
6468 ScriptServer::thread_enter ();
69+ task_mutex.lock ();
6570 curr_thread.ready_for_scripting = true ;
6671 }
67- task_mutex.lock ();
6872 p_task->pool_thread_index = pool_thread_index;
6973 prev_task = curr_thread.current_task ;
7074 curr_thread.current_task = p_task;
@@ -389,83 +393,117 @@ Error WorkerThreadPool::wait_for_task_completion(TaskID p_task_id) {
389393 task_mutex.unlock ();
390394
391395 if (caller_pool_thread) {
392- while (true ) {
393- Task *task_to_process = nullptr ;
394- {
395- MutexLock lock (task_mutex);
396- bool was_signaled = caller_pool_thread->signaled ;
397- caller_pool_thread->signaled = false ;
398-
399- if (task->completed ) {
400- // This thread was awaken also for some reason, but it's about to exit.
401- // Let's find out what may be pending and forward the requests.
402- if (!exit_threads && was_signaled) {
403- uint32_t to_process = task_queue.first () ? 1 : 0 ;
404- uint32_t to_promote = caller_pool_thread->current_task ->low_priority && low_priority_task_queue.first () ? 1 : 0 ;
405- if (to_process || to_promote) {
406- // This thread must be left alone since it won't loop again.
407- caller_pool_thread->signaled = true ;
408- _notify_threads (caller_pool_thread, to_process, to_promote);
409- }
410- }
396+ _wait_collaboratively (caller_pool_thread, task);
397+ task->waiting_pool --;
398+ if (task->waiting_pool == 0 && task->waiting_user == 0 ) {
399+ tasks.erase (p_task_id);
400+ task_allocator.free (task);
401+ }
402+ } else {
403+ task->done_semaphore .wait ();
404+ task_mutex.lock ();
405+ task->waiting_user --;
406+ if (task->waiting_pool == 0 && task->waiting_user == 0 ) {
407+ tasks.erase (p_task_id);
408+ task_allocator.free (task);
409+ }
410+ task_mutex.unlock ();
411+ }
411412
412- task->waiting_pool --;
413- if (task->waiting_pool == 0 && task->waiting_user == 0 ) {
414- tasks.erase (p_task_id);
415- task_allocator.free (task);
416- }
413+ return OK ;
414+ }
417415
418- break ;
419- }
416+ void WorkerThreadPool::_wait_collaboratively (ThreadData *p_caller_pool_thread, Task *p_task) {
417+ // Keep processing tasks until the condition to stop waiting is met.
420418
421- if (!exit_threads) {
422- // This is a thread from the pool. It shouldn't just idle.
423- // Let's try to process other tasks while we wait.
419+ #define IS_WAIT_OVER (unlikely(p_task == ThreadData::YIELDING ) ? p_caller_pool_thread->yield_is_over : p_task->completed)
424420
425- if (caller_pool_thread->current_task ->low_priority && low_priority_task_queue.first ()) {
426- if (_try_promote_low_priority_task ()) {
427- _notify_threads (caller_pool_thread, 1 , 0 );
428- }
421+ while (true ) {
422+ Task *task_to_process = nullptr ;
423+ {
424+ MutexLock lock (task_mutex);
425+ bool was_signaled = p_caller_pool_thread->signaled ;
426+ p_caller_pool_thread->signaled = false ;
427+
428+ if (IS_WAIT_OVER ) {
429+ p_caller_pool_thread->yield_is_over = false ;
430+ if (!exit_threads && was_signaled) {
431+ // This thread was awaken for some additional reason, but it's about to exit.
432+ // Let's find out what may be pending and forward the requests.
433+ uint32_t to_process = task_queue.first () ? 1 : 0 ;
434+ uint32_t to_promote = p_caller_pool_thread->current_task ->low_priority && low_priority_task_queue.first () ? 1 : 0 ;
435+ if (to_process || to_promote) {
436+ // This thread must be left alone since it won't loop again.
437+ p_caller_pool_thread->signaled = true ;
438+ _notify_threads (p_caller_pool_thread, to_process, to_promote);
429439 }
440+ }
441+
442+ break ;
443+ }
430444
431- if (singleton->task_queue .first ()) {
432- task_to_process = task_queue.first ()->self ();
433- task_queue.remove (task_queue.first ());
445+ if (!exit_threads) {
446+ if (p_caller_pool_thread->current_task ->low_priority && low_priority_task_queue.first ()) {
447+ if (_try_promote_low_priority_task ()) {
448+ _notify_threads (p_caller_pool_thread, 1 , 0 );
434449 }
450+ }
435451
436- if (!task_to_process) {
437- caller_pool_thread->awaited_task = task;
452+ if (singleton->task_queue .first ()) {
453+ task_to_process = task_queue.first ()->self ();
454+ task_queue.remove (task_queue.first ());
455+ }
438456
439- if (flushing_cmd_queue) {
440- flushing_cmd_queue->unlock ();
441- }
442- caller_pool_thread->cond_var .wait (lock);
443- if (flushing_cmd_queue) {
444- flushing_cmd_queue->lock ();
445- }
457+ if (!task_to_process) {
458+ p_caller_pool_thread->awaited_task = p_task;
446459
447- DEV_ASSERT (exit_threads || caller_pool_thread->signaled || task->completed );
448- caller_pool_thread->awaited_task = nullptr ;
460+ if (flushing_cmd_queue) {
461+ flushing_cmd_queue->unlock ();
462+ }
463+ p_caller_pool_thread->cond_var .wait (lock);
464+ if (flushing_cmd_queue) {
465+ flushing_cmd_queue->lock ();
449466 }
450- }
451- }
452467
453- if (task_to_process) {
454- _process_task (task_to_process);
468+ DEV_ASSERT (exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER );
469+ p_caller_pool_thread->awaited_task = nullptr ;
470+ }
455471 }
456472 }
457- } else {
458- task->done_semaphore .wait ();
459- task_mutex.lock ();
460- task->waiting_user --;
461- if (task->waiting_pool == 0 && task->waiting_user == 0 ) {
462- tasks.erase (p_task_id);
463- task_allocator.free (task);
473+
474+ if (task_to_process) {
475+ _process_task (task_to_process);
464476 }
477+ }
478+ }
479+
480+ void WorkerThreadPool::yield () {
481+ int th_index = get_thread_index ();
482+ ERR_FAIL_COND_MSG (th_index == -1 , " This function can only be called from a worker thread." );
483+ _wait_collaboratively (&threads[th_index], ThreadData::YIELDING );
484+ }
485+
486+ void WorkerThreadPool::notify_yield_over (TaskID p_task_id) {
487+ task_mutex.lock ();
488+ Task **taskp = tasks.getptr (p_task_id);
489+ if (!taskp) {
465490 task_mutex.unlock ();
491+ ERR_FAIL_MSG (" Invalid Task ID." );
466492 }
493+ Task *task = *taskp;
467494
468- return OK ;
495+ #ifdef DEBUG_ENABLED
496+ if (task->pool_thread_index == get_thread_index ()) {
497+ WARN_PRINT (" A worker thread is attempting to notify itself. That makes no sense." );
498+ }
499+ #endif
500+
501+ ThreadData &td = threads[task->pool_thread_index ];
502+ td.yield_is_over = true ;
503+ td.signaled = true ;
504+ td.cond_var .notify_one ();
505+
506+ task_mutex.unlock ();
469507}
470508
471509WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task (const Callable &p_callable, void (*p_func)(void *, uint32_t ), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) {
0 commit comments