Skip to content

Commit c951421

Browse files
authored
Merge pull request #90268 from RandomShaper/wtp_servers
Use WorkerThreadPool for Server threads (enhanced)
2 parents a44b0b6 + 65686de commit c951421

38 files changed

Lines changed: 457 additions & 389 deletions

core/config/engine.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ int Engine::get_audio_output_latency() const {
8282
return _audio_output_latency;
8383
}
8484

85+
void Engine::increment_frames_drawn() {
86+
if (frame_server_synced) {
87+
server_syncs++;
88+
} else {
89+
server_syncs = 0;
90+
}
91+
frame_server_synced = false;
92+
93+
frames_drawn++;
94+
}
95+
8596
uint64_t Engine::get_frames_drawn() {
8697
return frames_drawn;
8798
}
@@ -364,6 +375,11 @@ Engine *Engine::get_singleton() {
364375
return singleton;
365376
}
366377

378+
bool Engine::notify_frame_server_synced() {
379+
frame_server_synced = true;
380+
return server_syncs > SERVER_SYNC_FRAME_COUNT_WARNING;
381+
}
382+
367383
Engine::Engine() {
368384
singleton = this;
369385
}

core/config/engine.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ class Engine {
9191
String write_movie_path;
9292
String shader_cache_path;
9393

94+
static constexpr int SERVER_SYNC_FRAME_COUNT_WARNING = 5;
95+
int server_syncs = 0;
96+
bool frame_server_synced = false;
97+
9498
public:
9599
static Engine *get_singleton();
96100

@@ -179,6 +183,9 @@ class Engine {
179183
bool is_generate_spirv_debug_info_enabled() const;
180184
int32_t get_gpu_index() const;
181185

186+
void increment_frames_drawn();
187+
bool notify_frame_server_synced();
188+
182189
Engine();
183190
virtual ~Engine() {}
184191
};

core/object/worker_thread_pool.cpp

Lines changed: 98 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
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+
3840
void 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

471509
WorkerThreadPool::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) {

core/object/worker_thread_pool.h

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,21 @@ class WorkerThreadPool : public Object {
107107
BinaryMutex task_mutex;
108108

109109
struct ThreadData {
110+
static Task *const YIELDING; // Too bad constexpr doesn't work here.
111+
110112
uint32_t index = 0;
111113
Thread thread;
112-
bool ready_for_scripting = false;
113-
bool signaled = false;
114+
bool ready_for_scripting : 1;
115+
bool signaled : 1;
116+
bool yield_is_over : 1;
114117
Task *current_task = nullptr;
115-
Task *awaited_task = nullptr; // Null if not awaiting the condition variable. Special value for idle-waiting.
118+
Task *awaited_task = nullptr; // Null if not awaiting the condition variable, or special value (YIELDING).
116119
ConditionVariable cond_var;
120+
121+
ThreadData() :
122+
ready_for_scripting(false),
123+
signaled(false),
124+
yield_is_over(false) {}
117125
};
118126

119127
TightLocalVector<ThreadData> threads;
@@ -177,6 +185,8 @@ class WorkerThreadPool : public Object {
177185
}
178186
};
179187

188+
void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task);
189+
180190
protected:
181191
static void _bind_methods();
182192

@@ -196,6 +206,9 @@ class WorkerThreadPool : public Object {
196206
bool is_task_completed(TaskID p_task_id) const;
197207
Error wait_for_task_completion(TaskID p_task_id);
198208

209+
void yield();
210+
void notify_yield_over(TaskID p_task_id);
211+
199212
template <typename C, typename M, typename U>
200213
GroupID add_template_group_task(C *p_instance, M p_method, U p_userdata, int p_elements, int p_tasks = -1, bool p_high_priority = false, const String &p_description = String()) {
201214
typedef GroupUserData<C, M, U> GroupUD;

core/templates/command_queue_mt.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,8 @@ CommandQueueMT::SyncSemaphore *CommandQueueMT::_alloc_sync_sem() {
7070
return &sync_sems[idx];
7171
}
7272

73-
CommandQueueMT::CommandQueueMT(bool p_sync) {
74-
if (p_sync) {
75-
sync = memnew(Semaphore);
76-
}
73+
CommandQueueMT::CommandQueueMT() {
7774
}
7875

7976
CommandQueueMT::~CommandQueueMT() {
80-
if (sync) {
81-
memdelete(sync);
82-
}
8377
}

core/templates/command_queue_mt.h

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -248,16 +248,17 @@
248248
#define CMD_TYPE(N) Command##N<T, M COMMA(N) COMMA_SEP_LIST(TYPE_ARG, N)>
249249
#define CMD_ASSIGN_PARAM(N) cmd->p##N = p##N
250250

251-
#define DECL_PUSH(N) \
252-
template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
253-
void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
254-
CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
255-
cmd->instance = p_instance; \
256-
cmd->method = p_method; \
257-
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
258-
unlock(); \
259-
if (sync) \
260-
sync->post(); \
251+
#define DECL_PUSH(N) \
252+
template <typename T, typename M COMMA(N) COMMA_SEP_LIST(TYPE_PARAM, N)> \
253+
void push(T *p_instance, M p_method COMMA(N) COMMA_SEP_LIST(PARAM, N)) { \
254+
CMD_TYPE(N) *cmd = allocate_and_lock<CMD_TYPE(N)>(); \
255+
cmd->instance = p_instance; \
256+
cmd->method = p_method; \
257+
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
258+
if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
259+
WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
260+
} \
261+
unlock(); \
261262
}
262263

263264
#define CMD_RET_TYPE(N) CommandRet##N<T, M, COMMA_SEP_LIST(TYPE_ARG, N) COMMA(N) R>
@@ -272,9 +273,10 @@
272273
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
273274
cmd->ret = r_ret; \
274275
cmd->sync_sem = ss; \
276+
if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
277+
WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
278+
} \
275279
unlock(); \
276-
if (sync) \
277-
sync->post(); \
278280
ss->sem.wait(); \
279281
ss->in_use = false; \
280282
}
@@ -290,9 +292,10 @@
290292
cmd->method = p_method; \
291293
SEMIC_SEP_LIST(CMD_ASSIGN_PARAM, N); \
292294
cmd->sync_sem = ss; \
295+
if (pump_task_id != WorkerThreadPool::INVALID_TASK_ID) { \
296+
WorkerThreadPool::get_singleton()->notify_yield_over(pump_task_id); \
297+
} \
293298
unlock(); \
294-
if (sync) \
295-
sync->post(); \
296299
ss->sem.wait(); \
297300
ss->in_use = false; \
298301
}
@@ -340,7 +343,7 @@ class CommandQueueMT {
340343
LocalVector<uint8_t> command_mem;
341344
SyncSemaphore sync_sems[SYNC_SEMAPHORES];
342345
Mutex mutex;
343-
Semaphore *sync = nullptr;
346+
WorkerThreadPool::TaskID pump_task_id = WorkerThreadPool::INVALID_TASK_ID;
344347
uint64_t flush_read_ptr = 0;
345348

346349
template <typename T>
@@ -420,12 +423,16 @@ class CommandQueueMT {
420423
}
421424

422425
void wait_and_flush() {
423-
ERR_FAIL_NULL(sync);
424-
sync->wait();
426+
ERR_FAIL_COND(pump_task_id == WorkerThreadPool::INVALID_TASK_ID);
427+
WorkerThreadPool::get_singleton()->wait_for_task_completion(pump_task_id);
425428
_flush();
426429
}
427430

428-
CommandQueueMT(bool p_sync);
431+
void set_pump_task_id(WorkerThreadPool::TaskID p_task_id) {
432+
pump_task_id = p_task_id;
433+
}
434+
435+
CommandQueueMT();
429436
~CommandQueueMT();
430437
};
431438

0 commit comments

Comments
 (0)