Browse Source

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

Pedro J. Estébanez 1 year ago
parent
commit
ec61c5064c
3 changed files with 42 additions and 1 deletions
  1. 37 0
      core/io/resource_loader.cpp
  2. 3 0
      core/io/resource_loader.h
  3. 2 1
      doc/classes/ResourceLoader.xml

+ 37 - 0
core/io/resource_loader.cpp

@@ -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)
@@ -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;
 }
 
@@ -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();
+				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);
@@ -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);

+ 3 - 0
core/io/resource_loader.h

@@ -174,6 +174,7 @@ private:
 		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;
@@ -197,6 +198,8 @@ private:
 
 	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);

+ 2 - 1
doc/classes/ResourceLoader.xml

@@ -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">
@@ -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">