Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion core/object/worker_thread_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -580,9 +580,11 @@ void WorkerThreadPool::_switch_runlevel(Runlevel p_runlevel) {
runlevel = p_runlevel;
memset(&runlevel_data, 0, sizeof(runlevel_data));
for (uint32_t i = 0; i < threads.size(); i++) {
threads[i].cond_var.notify_one();
threads[i].cond_var.notify_one(); // Wakes the worker loop so it rechecks the new runlevel
threads[i].signaled = true;
}
// Notify the main thread even if counts already match,
// in case it's currently blocked in wait_for_usec / wait.
control_cond_var.notify_all();
}

Expand Down
21 changes: 20 additions & 1 deletion core/os/condition_variable.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
#define THREADING_NAMESPACE std
#endif

#include <chrono>

/// An object one or multiple threads can wait on a be notified by some other.
/// Normally, you want to use a semaphore for such scenarios, but when the
/// condition is something different than a count being greater than zero
Expand All @@ -71,6 +73,17 @@ class ConditionVariable {
condition.wait(p_lock.mutex._get_lock());
}

/// @return `true` if notified before timeout, false if timed out.
template <typename BinaryMutexT>
_ALWAYS_INLINE_ bool wait_for_usec(const MutexLock<BinaryMutexT> &p_lock, uint64_t p_usec) const {
return condition.wait_for(p_lock._get_lock(), std::chrono::microseconds(p_usec)) == THREADING_NAMESPACE::cv_status::no_timeout;
}

template <int Tag>
_ALWAYS_INLINE_ bool wait_for_usec(const MutexLock<SafeBinaryMutex<Tag>> &p_lock, uint64_t p_usec) const {
return condition.wait_for(p_lock.mutex._get_lock(), std::chrono::microseconds(p_usec)) == THREADING_NAMESPACE::cv_status::no_timeout;
}

_ALWAYS_INLINE_ void notify_one() const {
condition.notify_one();
}
Expand All @@ -81,13 +94,19 @@ class ConditionVariable {
};

#else // No threads.

class ConditionVariable {
public:
template <typename BinaryMutexT>
void wait(const MutexLock<BinaryMutexT> &p_lock) const {}
template <int Tag>
void wait(const MutexLock<SafeBinaryMutex<Tag>> &p_lock) const {}
void notify_one() const {}
void notify_all() const {}

template <typename BinaryMutexT>
bool wait_for_usec(const MutexLock<BinaryMutexT> &p_lock, uint64_t p_usec) const { return true; }
template <int Tag>
bool wait_for_usec(const MutexLock<SafeBinaryMutex<Tag>> &p_lock, uint64_t p_usec) const { return true; }
};

#endif // THREADS_ENABLED
55 changes: 41 additions & 14 deletions drivers/gles3/rasterizer_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,23 +489,13 @@ void RasterizerGLES3::set_boot_image(const Ref<Image> &p_image, const Color &p_c
texture_storage->texture_2d_initialize(texture, p_image);

Rect2 imgrect(0, 0, p_image->get_width(), p_image->get_height());
// Size2 win_size_f = Size2(win_size); // This is needed for the .floor() function below because Size2i does not have a floor() function (but Size2 does)
Rect2 screenrect;
if (p_scale) {
if (win_size.width > win_size.height) {
//scale horizontally
screenrect.size.y = win_size.height;
screenrect.size.x = imgrect.size.x * win_size.height / imgrect.size.y;
screenrect.position.x = (win_size.width - screenrect.size.x) / 2;

} else {
//scale vertically
screenrect.size.x = win_size.width;
screenrect.size.y = imgrect.size.y * win_size.width / imgrect.size.x;
screenrect.position.y = (win_size.height - screenrect.size.y) / 2;
}
if (p_scale) {
screenrect = OS::get_singleton()->calculate_boot_screen_rect(win_size, imgrect.size);
} else {
screenrect = imgrect;
screenrect.position += ((Size2(win_size.width, win_size.height) - screenrect.size) / 2.0).floor();
screenrect = DisplayServer::calculate_boot_image_rect(win_size, imgrect);
}

#ifdef WINDOWS_ENABLED
Expand All @@ -530,6 +520,43 @@ void RasterizerGLES3::set_boot_image(const Ref<Image> &p_image, const Color &p_c

gl_end_frame(true);

// After the first commit, a tiling compositor (e.g. Hyprland via XWayland)
// may have sent a ConfigureNotify with the actual window size.
// Pump events and re-render if the size changed.
DisplayServer::get_singleton()->pump_resize_events();
Size2i new_win_size = DisplayServer::get_singleton()->window_get_size();

if (new_win_size != win_size && new_win_size.width > 0 && new_win_size.height > 0) {
win_size = new_win_size;
glViewport(0, 0, win_size.width, win_size.height);
glClearColor(p_color.r, p_color.g, p_color.b,
OS::get_singleton()->is_layered_allowed() ? p_color.a : 1.0);
glClear(GL_COLOR_BUFFER_BIT);

Rect2 screenrect2;
if (p_scale) {
screenrect2 = OS::get_singleton()->calculate_boot_screen_rect(win_size, imgrect.size);
} else {
screenrect2 = DisplayServer::calculate_boot_image_rect(win_size, imgrect);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#ifdef WINDOWS_ENABLED
if (!screen_flipped_y)
#endif
{
screenrect2.position.y = win_size.y - screenrect2.position.y;
screenrect2.size.y = -screenrect2.size.y;
}
screenrect2.position /= win_size;
screenrect2.size /= win_size;

GLES3::Texture *t2 = texture_storage->get_texture(texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, t2->tex_id);
copy_effects->copy_to_rect(screenrect2);
glBindTexture(GL_TEXTURE_2D, 0);
gl_end_frame(true);
}

texture_storage->texture_free(texture);
}

Expand Down
55 changes: 54 additions & 1 deletion editor/doc/editor_help.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3061,6 +3061,8 @@ void EditorHelp::_load_doc_thread(void *p_udata) {
}

OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));

_worker_thread_done.set();
}

void EditorHelp::_gen_doc_thread(void *p_udata) {
Expand Down Expand Up @@ -3094,6 +3096,8 @@ void EditorHelp::_gen_doc_thread(void *p_udata) {
}

OS::get_singleton()->benchmark_end_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));

_worker_thread_done.set();
}

void EditorHelp::_gen_extensions_docs() {
Expand All @@ -3109,6 +3113,10 @@ static void _load_script_doc_cache(bool p_changes) {
}

void EditorHelp::load_script_doc_cache() {
if (_cleanup_in_progress.is_set()) {
return;
}

if (!ProjectSettings::get_singleton()->is_project_loaded()) {
print_verbose("Skipping loading script doc cache since no project is open.");
return;
Expand All @@ -3132,6 +3140,7 @@ void EditorHelp::load_script_doc_cache() {
return;
}

_worker_thread_done.set_to(false);
worker_thread.start(_load_script_doc_cache_thread, nullptr);
}

Expand All @@ -3152,13 +3161,18 @@ void EditorHelp::_process_postponed_docs() {

void EditorHelp::_load_script_doc_cache_thread(void *p_udata) {
ERR_FAIL_COND_MSG(!ProjectSettings::get_singleton()->is_project_loaded(), "Error: cannot load script doc cache without a project.");
_worker_thread_done.set();
return;
ERR_FAIL_COND_MSG(!ResourceLoader::exists(get_script_doc_cache_full_path()), "Error: cannot load script doc cache from inexistent file.");
_worker_thread_done.set();
return;

Ref<Resource> script_doc_cache_res = ResourceLoader::load(get_script_doc_cache_full_path(), "", ResourceFormatLoader::CACHE_MODE_IGNORE);
if (script_doc_cache_res.is_null()) {
print_verbose("Script doc cache is corrupted. Regenerating it instead.");
_delete_script_doc_cache();
callable_mp_static(EditorHelp::regenerate_script_doc_cache).call_deferred();
_worker_thread_done.set();
return;
}

Expand All @@ -3175,6 +3189,8 @@ void EditorHelp::_load_script_doc_cache_thread(void *p_udata) {

// Always delete the doc cache after successful load since most uses of editor will change a script, invalidating cache.
_delete_script_doc_cache();

_worker_thread_done.set();
}

/// Helper method to deal with "sources_changed" signal having a parameter.
Expand All @@ -3183,6 +3199,10 @@ static void _regenerate_script_doc_cache(bool p_changes) {
}

void EditorHelp::regenerate_script_doc_cache() {
if (_cleanup_in_progress.is_set()) {
return;
}

if (EditorFileSystem::get_singleton()->is_scanning()) {
// Wait until EditorFileSystem scanning is complete to use updated filesystem structure.
EditorFileSystem::get_singleton()->connect(SNAME("sources_changed"), callable_mp_static(_regenerate_script_doc_cache), CONNECT_ONE_SHOT);
Expand All @@ -3191,6 +3211,7 @@ void EditorHelp::regenerate_script_doc_cache() {

_wait_for_thread(worker_thread);
_wait_for_thread(loader_thread);
_loader_thread_done.set_to(false);
loader_thread.start(_regen_script_doc_thread, EditorFileSystem::get_singleton()->get_filesystem());
}

Expand All @@ -3200,6 +3221,8 @@ void EditorHelp::_finish_regen_script_doc_thread(void *p_udata) {
_script_docs_loaded.set();

OS::get_singleton()->benchmark_end_measure("EditorHelp", "Generate Script Documentation");

_worker_thread_done.set();
}

void EditorHelp::_regen_script_doc_thread(void *p_udata) {
Expand All @@ -3215,6 +3238,9 @@ void EditorHelp::_regen_script_doc_thread(void *p_udata) {

_reload_scripts_documentation(dir);

_loader_thread_done.set();
_worker_thread_done.set_to(false); // worker_thread is about to start

// All ResourceLoader::load() calls are done, so we can no longer deadlock with main thread.
// Switch to back to worker_thread from loader_thread to resynchronize access to DocData.
worker_thread.start(_finish_regen_script_doc_thread, nullptr);
Expand Down Expand Up @@ -3265,6 +3291,10 @@ void EditorHelp::save_script_doc_cache() {
}

void EditorHelp::generate_doc(bool p_use_cache, bool p_use_script_cache) {
if (_cleanup_in_progress.is_set()) {
return;
}

doc_generation_count++;
OS::get_singleton()->benchmark_begin_measure("EditorHelp", vformat("Generate Documentation (Run %d)", doc_generation_count));

Expand All @@ -3280,10 +3310,12 @@ void EditorHelp::generate_doc(bool p_use_cache, bool p_use_script_cache) {
}

if (p_use_cache && FileAccess::exists(get_cache_full_path())) {
_worker_thread_done.set_to(false);
worker_thread.start(_load_doc_thread, (void *)p_use_script_cache);
} else {
print_verbose("Regenerating editor help cache");
doc->generate();
_worker_thread_done.set_to(false);
worker_thread.start(_gen_doc_thread, (void *)p_use_script_cache);
}
}
Expand Down Expand Up @@ -3376,7 +3408,28 @@ void EditorHelp::update_doc() {
}

void EditorHelp::cleanup_doc() {
_wait_for_thread();
_cleanup_in_progress.set();

// loader_thread runs _regen_script_doc_thread which calls ResourceLoader::load().
// Flush the message queue so load tasks can dispatch to the main thread.
if (loader_thread.is_started()) {
while (!_loader_thread_done.is_set()) {
MessageQueue::get_singleton()->flush();
OS::get_singleton()->delay_usec(1000);
}
loader_thread.wait_to_finish();
}

// worker_thread runs _load_doc_thread, _gen_doc_thread, _load_script_doc_cache_thread,
// or _finish_regen_script_doc_thread. All may post deferred calls needing the main thread.
if (worker_thread.is_started()) {
while (!_worker_thread_done.is_set()) {
MessageQueue::get_singleton()->flush();
OS::get_singleton()->delay_usec(1000);
}
worker_thread.wait_to_finish();
}

memdelete(doc);
doc = nullptr;
}
Expand Down
4 changes: 4 additions & 0 deletions editor/doc/editor_help.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ class EditorHelp : public VBoxContainer {
inline static Thread loader_thread; ///< Only load scripts here to avoid deadlocking with main thread.

inline static SafeFlag _script_docs_loaded = SafeFlag(false);
inline static SafeFlag _worker_thread_done = SafeFlag(false);
inline static SafeFlag _loader_thread_done = SafeFlag(false);
inline static SafeFlag _cleanup_in_progress = SafeFlag(false);

inline static LocalVector<DocData::ClassDoc> _docs_to_add;
inline static LocalVector<String> _docs_to_remove;
inline static LocalVector<String> _docs_to_remove_by_path;
Expand Down
3 changes: 2 additions & 1 deletion editor/file_system/editor_file_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1730,8 +1730,9 @@ void EditorFileSystem::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
Thread &active_thread = thread.is_started() ? thread : thread_sources;
if (use_threads && active_thread.is_started()) {
const uint64_t TIMEOUT_USEC = 1000;
while (scanning) {
OS::get_singleton()->delay_usec(1000);
OS::get_singleton()->delay_usec(TIMEOUT_USEC);
}
active_thread.wait_to_finish();
WARN_PRINT("Scan thread aborted...");
Expand Down
8 changes: 7 additions & 1 deletion editor/inspector/editor_resource_preview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -600,8 +600,14 @@ void EditorResourcePreview::stop() {
preview_generators.write[i]->abort();
}

const uint64_t TIMEOUT_USEC = 3000000; // 3 seconds
uint64_t start = OS::get_singleton()->get_ticks_usec();

while (!exited.is_set()) {
// Sync pending work.
if (OS::get_singleton()->get_ticks_usec() - start > TIMEOUT_USEC) {
WARN_PRINT("EditorResourcePreview: Timed out waiting for preview thread.");
break;
}
OS::get_singleton()->delay_usec(10000);
RenderingServer::get_singleton()->sync();
MessageQueue::get_singleton()->flush();
Expand Down
Loading
Loading