Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResourceLoader: Support polling and get-before-complete on the main thread #93695

Merged
merged 1 commit into from
Jun 28, 2024
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
37 changes: 37 additions & 0 deletions core/io/resource_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "core/string/print_string.h"
#include "core/string/translation.h"
#include "core/variant/variant_parser.h"
#include "servers/rendering_server.h"

#ifdef DEBUG_LOAD_THREADED
#define print_lt(m_text) print_line(m_text)
Expand Down Expand Up @@ -585,6 +586,16 @@ ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const
*r_progress = _dependency_get_progress(local_path);
}

// Support userland polling in a loop on the main thread.
if (Thread::is_main_thread() && status == THREAD_LOAD_IN_PROGRESS) {
uint64_t frame = Engine::get_singleton()->get_process_frames();
if (frame == load_task.last_progress_check_main_thread_frame) {
_ensure_load_progress();
} else {
load_task.last_progress_check_main_thread_frame = frame;
}
}

return status;
}

Expand Down Expand Up @@ -613,6 +624,21 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e
}
return Ref<Resource>();
}

// Support userland requesting on the main thread before the load is reported to be complete.
if (Thread::is_main_thread() && !load_token->local_path.is_empty()) {
const ThreadLoadTask &load_task = thread_load_tasks[load_token->local_path];
while (load_task.status == THREAD_LOAD_IN_PROGRESS) {
if (!_ensure_load_progress()) {
// This local poll loop is not needed.
break;
}
thread_load_lock.~MutexLock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me think a branch I made some time ago to allow unlocking and re-locking of MutexLock is needed, it was just a hypothetical idea then but will revive it and open a PR if it works well still, to avoid potentially hacky solutions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, but I think this may not be really a good thing to do depending on the kind of mutex now we have #93701. I haven't checked it yet, but I'm suspicious of this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue was something else in the end.

OS::get_singleton()->delay_usec(1000);
new (&thread_load_lock) MutexLock(thread_load_mutex);
}
}

res = _load_complete_inner(*load_token, r_error, thread_load_lock);
if (load_token->unreference()) {
memdelete(load_token);
Expand Down Expand Up @@ -731,6 +757,17 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
}
}

bool ResourceLoader::_ensure_load_progress() {
// Some servers may need a new engine iteration to allow the load to progress.
// Since the only known one is the rendering server (in single thread mode), let's keep it simple and just sync it.
// This may be refactored in the future to support other servers and have less coupling.
if (OS::get_singleton()->get_render_thread_mode() == OS::RENDER_SEPARATE_THREAD) {
return false; // Not needed.
}
RenderingServer::get_singleton()->sync();
return true;
}

Ref<Resource> ResourceLoader::ensure_resource_ref_override_for_outer_load(const String &p_path, const String &p_res_type) {
ERR_FAIL_COND_V(load_nesting == 0, Ref<Resource>()); // It makes no sense to use this from nesting level 0.
const String &local_path = _validate_local_path(p_path);
Expand Down
3 changes: 3 additions & 0 deletions core/io/resource_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class ResourceLoader {
String type_hint;
float progress = 0.0f;
float max_reported_progress = 0.0f;
uint64_t last_progress_check_main_thread_frame = UINT64_MAX;
ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS;
ResourceFormatLoader::CacheMode cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE;
Error error = OK;
Expand All @@ -197,6 +198,8 @@ class ResourceLoader {

static float _dependency_get_progress(const String &p_path);

static bool _ensure_load_progress();

public:
static Error load_threaded_request(const String &p_path, const String &p_type_hint = "", bool p_use_sub_threads = false, ResourceFormatLoader::CacheMode p_cache_mode = ResourceFormatLoader::CACHE_MODE_REUSE);
static ThreadLoadStatus load_threaded_get_status(const String &p_path, float *r_progress = nullptr);
Expand Down
3 changes: 2 additions & 1 deletion doc/classes/ResourceLoader.xml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
<param index="0" name="path" type="String" />
<description>
Returns the resource loaded by [method load_threaded_request].
If this is called before the loading thread is done (i.e. [method load_threaded_get_status] is not [constant THREAD_LOAD_LOADED]), the calling thread will be blocked until the resource has finished loading.
If this is called before the loading thread is done (i.e. [method load_threaded_get_status] is not [constant THREAD_LOAD_LOADED]), the calling thread will be blocked until the resource has finished loading. However, it's recommended to use [method load_threaded_get_status] to known when the load has actually completed.
</description>
</method>
<method name="load_threaded_get_status">
Expand All @@ -97,6 +97,7 @@
<description>
Returns the status of a threaded loading operation started with [method load_threaded_request] for the resource at [param path]. See [enum ThreadLoadStatus] for possible return values.
An array variable can optionally be passed via [param progress], and will return a one-element array containing the percentage of completion of the threaded loading.
[b]Note:[/b] The recommended way of using this method is to call it during different frames (e.g., in [method Node._process], instead of a loop).
</description>
</method>
<method name="load_threaded_request">
Expand Down
Loading