feat: updated engine
This commit is contained in:
parent
cbe99774ff
commit
f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions
|
|
@ -30,11 +30,15 @@
|
|||
|
||||
#include "resource_loader.h"
|
||||
|
||||
#include "core/config/engine.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/core_bind.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/resource_importer.h"
|
||||
#include "core/object/callable_mp.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/message_queue.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/condition_variable.h"
|
||||
#include "core/os/os.h"
|
||||
|
|
@ -218,26 +222,6 @@ void ResourceFormatLoader::_bind_methods() {
|
|||
GDVIRTUAL_BIND(_load, "path", "original_path", "use_sub_threads", "cache_mode");
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
// These are used before and after a wait for a WorkerThreadPool task
|
||||
// because that can lead to another load started in the same thread,
|
||||
// something we must treat as a different stack for the purposes
|
||||
// of tracking nesting.
|
||||
|
||||
#define PREPARE_FOR_WTP_WAIT \
|
||||
int load_nesting_backup = ResourceLoader::load_nesting; \
|
||||
Vector<String> load_paths_stack_backup = ResourceLoader::load_paths_stack; \
|
||||
ResourceLoader::load_nesting = 0; \
|
||||
ResourceLoader::load_paths_stack.clear();
|
||||
|
||||
#define RESTORE_AFTER_WTP_WAIT \
|
||||
DEV_ASSERT(ResourceLoader::load_nesting == 0); \
|
||||
DEV_ASSERT(ResourceLoader::load_paths_stack.is_empty()); \
|
||||
ResourceLoader::load_nesting = load_nesting_backup; \
|
||||
ResourceLoader::load_paths_stack = load_paths_stack_backup; \
|
||||
load_paths_stack_backup.clear();
|
||||
|
||||
// This should be robust enough to be called redundantly without issues.
|
||||
void ResourceLoader::LoadToken::clear() {
|
||||
WorkerThreadPool::TaskID task_to_await = 0;
|
||||
|
|
@ -276,9 +260,11 @@ void ResourceLoader::LoadToken::clear() {
|
|||
|
||||
// If task is unused, await it here, locally, now the token data is consistent.
|
||||
if (task_to_await) {
|
||||
PREPARE_FOR_WTP_WAIT
|
||||
int load_nesting_backup = load_nesting;
|
||||
load_nesting = 0;
|
||||
WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await);
|
||||
RESTORE_AFTER_WTP_WAIT
|
||||
DEV_ASSERT(load_nesting == 0);
|
||||
load_nesting = load_nesting_backup;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -286,22 +272,11 @@ ResourceLoader::LoadToken::~LoadToken() {
|
|||
clear();
|
||||
}
|
||||
|
||||
Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) {
|
||||
Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, CacheMode p_cache_mode, Error *r_error, bool p_use_sub_threads, float *r_progress) {
|
||||
const String &original_path = p_original_path.is_empty() ? p_path : p_original_path;
|
||||
load_nesting++;
|
||||
if (load_paths_stack.size()) {
|
||||
MutexLock thread_load_lock(thread_load_mutex);
|
||||
const String &parent_task_path = load_paths_stack.get(load_paths_stack.size() - 1);
|
||||
HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(parent_task_path);
|
||||
// Avoid double-tracking, for progress reporting, resources that boil down to a remapped path containing the real payload (e.g., imported resources).
|
||||
bool is_remapped_load = original_path == parent_task_path;
|
||||
if (E && !is_remapped_load) {
|
||||
E->value.sub_tasks.insert(original_path);
|
||||
}
|
||||
}
|
||||
load_paths_stack.push_back(original_path);
|
||||
|
||||
print_verbose(vformat("Loading resource: %s", p_path));
|
||||
print_verbose(vformat("Loading resource: %s remapped: %s", p_path, _path_remap(p_path)));
|
||||
|
||||
// Try all loaders and pick the first match for the type hint
|
||||
bool found = false;
|
||||
|
|
@ -317,7 +292,6 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
|
|||
}
|
||||
}
|
||||
|
||||
load_paths_stack.resize(load_paths_stack.size() - 1);
|
||||
res_ref_overrides.erase(load_nesting);
|
||||
load_nesting--;
|
||||
|
||||
|
|
@ -362,22 +336,193 @@ Ref<Resource> ResourceLoader::_load(const String &p_path, const String &p_origin
|
|||
// The load task token must be manually re-referenced before this is called, which includes threaded runs.
|
||||
void ResourceLoader::_run_load_task(void *p_userdata) {
|
||||
ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
|
||||
int thread_index = WorkerThreadPool::get_singleton()->get_thread_index();
|
||||
String thread_waiting_on_backup;
|
||||
|
||||
bool wait = false;
|
||||
{
|
||||
MutexLock thread_load_lock(thread_load_mutex);
|
||||
if (cleaning_tasks) {
|
||||
load_task.status = THREAD_LOAD_FAILED;
|
||||
return;
|
||||
}
|
||||
|
||||
if (load_task.started_load && load_task.thread_index != thread_index) {
|
||||
// If we were already waiting for task completion in a previous
|
||||
// step make sure we don't clobber the old wait.
|
||||
if (thread_waiting_on.has(thread_index)) {
|
||||
thread_waiting_on_backup = thread_waiting_on[thread_index];
|
||||
}
|
||||
|
||||
thread_waiting_on[thread_index] = load_task.local_path;
|
||||
wait = true;
|
||||
} else {
|
||||
load_task.started_load = true;
|
||||
load_task.thread_index = thread_index;
|
||||
}
|
||||
}
|
||||
|
||||
ThreadLoadTask *curr_load_task_backup = curr_load_task;
|
||||
curr_load_task = &load_task;
|
||||
|
||||
if (wait) {
|
||||
// There are a couple of reasons why we got here:
|
||||
// 1) We re-started the task in _load_complete_inner but we also
|
||||
// got started via the original task in the WorkerThreadPool
|
||||
// 2) There's a race between multiple threads in _load_complete_inner
|
||||
// and more than one thread thought they had to restart
|
||||
|
||||
ThreadLoadStatus status;
|
||||
LocalVector<int> chain;
|
||||
|
||||
do {
|
||||
chain.clear();
|
||||
|
||||
thread_load_mutex.lock();
|
||||
|
||||
int waiting_on_thread = load_task.thread_index;
|
||||
int current_thread = waiting_on_thread;
|
||||
bool progress_blocked = thread_waiting_on.has(waiting_on_thread);
|
||||
|
||||
ThreadLoadTask *waiting_on_task = nullptr;
|
||||
bool cycle_detected = false;
|
||||
|
||||
// Try to figure out if we're in a dependency cycle, and what asset
|
||||
// we are ultimately waiting for.
|
||||
if (progress_blocked && thread_index != -1) {
|
||||
while (true) {
|
||||
String *waiting_on_path = thread_waiting_on.getptr(current_thread);
|
||||
if (!waiting_on_path) {
|
||||
break;
|
||||
}
|
||||
|
||||
waiting_on_task = thread_load_tasks.getptr(*waiting_on_path);
|
||||
if (!waiting_on_task) {
|
||||
// Path might be remapped, and someone might be waiting on the
|
||||
// remapped path.
|
||||
waiting_on_task = thread_load_tasks.getptr(_path_remap(*waiting_on_path));
|
||||
if (!waiting_on_task) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Record the cycle so we can determine whether we should be the one
|
||||
// to break it or not.
|
||||
int next_thread = waiting_on_task->thread_index;
|
||||
chain.push_back(current_thread);
|
||||
|
||||
// We made it back to us, we're in a cycle.
|
||||
if (next_thread == thread_index || chain.has(next_thread)) {
|
||||
cycle_detected = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (chain.size() > thread_load_tasks.size()) {
|
||||
ERR_PRINT(vformat("chain.size() > thread_load_tasks.size() for '%s' on thread %d",
|
||||
load_task.local_path, thread_index));
|
||||
cycle_detected = true;
|
||||
break;
|
||||
}
|
||||
|
||||
current_thread = next_thread;
|
||||
}
|
||||
}
|
||||
|
||||
status = load_task.status;
|
||||
|
||||
if (status == THREAD_LOAD_IN_PROGRESS) {
|
||||
if (cycle_detected) {
|
||||
// Only do something if we're the lowest thread ID waiting,
|
||||
// if we didn't we'd run the risk of concurrently running
|
||||
// the resource load again.
|
||||
int lowest_waiting = thread_index;
|
||||
for (const int &link : chain) {
|
||||
if (link < lowest_waiting) {
|
||||
lowest_waiting = link;
|
||||
}
|
||||
}
|
||||
|
||||
if (lowest_waiting == thread_index) {
|
||||
print_verbose(
|
||||
vformat("CYCLE: Stealing on thread %d for resource '%s' originally on thread %d",
|
||||
thread_index, load_task.local_path, waiting_on_task->thread_index));
|
||||
// Take over the task. The original thread was definitely
|
||||
// not going to make progress.
|
||||
load_task.thread_index = thread_index;
|
||||
thread_waiting_on.erase(thread_index);
|
||||
wait = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wait = false;
|
||||
}
|
||||
|
||||
// Only yield if we ultimately found a task that we are waiting on.
|
||||
bool should_yield = progress_blocked && wait && waiting_on_task && thread_index != -1;
|
||||
|
||||
if (should_yield) {
|
||||
// We need to make sure we yield on our actual current task. If we are
|
||||
// waiting we are certainly not the the task being ran.
|
||||
yielders.push_back(WorkerThreadPool::get_singleton()->get_caller_task_id());
|
||||
}
|
||||
|
||||
thread_load_mutex.unlock();
|
||||
|
||||
if (should_yield) {
|
||||
// We are blocked on some upstream task in our dependency chain. We
|
||||
// don't know how long it will take or what is needed to unblock it.
|
||||
// If we yield we give the WTP a free thread to solve the problem.
|
||||
int load_nesting_backup = load_nesting;
|
||||
load_nesting = 0;
|
||||
WorkerThreadPool::get_singleton()->yield();
|
||||
DEV_ASSERT(load_nesting == 0);
|
||||
load_nesting = load_nesting_backup;
|
||||
|
||||
thread_load_mutex.lock();
|
||||
yielders.erase(WorkerThreadPool::get_singleton()->get_caller_task_id());
|
||||
status = load_task.status;
|
||||
thread_load_mutex.unlock();
|
||||
} else if (wait) {
|
||||
// Forward progress is being made, just wait for task completion.
|
||||
// If we are not currently blocked more dependencies might block later,
|
||||
// so we cannot yield or wait on a task.
|
||||
// This is not the most optimal thing to do, but it is safe. Either the
|
||||
// dependency will complete soon, or will block soon when we can safely
|
||||
// yield.
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
}
|
||||
} while (wait && status == THREAD_LOAD_IN_PROGRESS && !cleaning_tasks);
|
||||
|
||||
if (cleaning_tasks || status != THREAD_LOAD_IN_PROGRESS) {
|
||||
curr_load_task = curr_load_task_backup;
|
||||
}
|
||||
|
||||
if (cleaning_tasks) {
|
||||
load_task.status = THREAD_LOAD_FAILED;
|
||||
// Do not attempt to unreference the load token. Many things are
|
||||
// tearing down concurrently and our task might be dead already. If it is
|
||||
// the load token is already released.
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != THREAD_LOAD_IN_PROGRESS) {
|
||||
load_task.load_token->unreference();
|
||||
thread_load_mutex.lock();
|
||||
if (thread_waiting_on_backup.is_empty()) {
|
||||
thread_waiting_on.erase(thread_index);
|
||||
} else {
|
||||
thread_waiting_on[thread_index] = thread_waiting_on_backup;
|
||||
}
|
||||
thread_load_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
// do it ourselves anyway
|
||||
}
|
||||
|
||||
// Thread-safe either if it's the current thread or a brand new one.
|
||||
CallQueue *own_mq_override = nullptr;
|
||||
if (load_nesting == 0) {
|
||||
DEV_ASSERT(load_paths_stack.is_empty());
|
||||
if (!Thread::is_main_thread()) {
|
||||
// Let the caller thread use its own, for added flexibility. Provide one otherwise.
|
||||
if (MessageQueue::get_singleton() == MessageQueue::get_main_singleton()) {
|
||||
|
|
@ -399,36 +544,41 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
|
|||
}
|
||||
|
||||
thread_load_mutex.lock();
|
||||
bool thread_load_mutex_held = true;
|
||||
|
||||
bool was_finished = load_task.finished_load;
|
||||
if (load_task.resource.is_valid()) {
|
||||
load_task.finished_load = true;
|
||||
}
|
||||
|
||||
load_task.resource = res;
|
||||
|
||||
load_task.progress = 1.0; // It was fully loaded at this point, so force progress to 1.0.
|
||||
load_task.error = load_err;
|
||||
if (load_task.error != OK) {
|
||||
load_task.status = THREAD_LOAD_FAILED;
|
||||
} else {
|
||||
load_task.status = THREAD_LOAD_LOADED;
|
||||
}
|
||||
|
||||
if (load_task.cond_var && load_task.need_wait) {
|
||||
load_task.cond_var->notify_all();
|
||||
}
|
||||
load_task.need_wait = false;
|
||||
|
||||
bool ignoring = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP;
|
||||
bool replacing = load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE || load_task.cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE_DEEP;
|
||||
bool unlock_pending = true;
|
||||
bool ignoring = load_task.cache_mode == CACHE_MODE_IGNORE || load_task.cache_mode == CACHE_MODE_IGNORE_DEEP;
|
||||
bool replacing = load_task.cache_mode == CACHE_MODE_REPLACE || load_task.cache_mode == CACHE_MODE_REPLACE_DEEP;
|
||||
if (load_task.resource.is_valid()) {
|
||||
// From now on, no critical section needed as no one will write to the task anymore.
|
||||
// Moreover, the mutex being unlocked is a requirement if some of the calls below
|
||||
// that set the resource up invoke code that in turn requests resource loading.
|
||||
thread_load_mutex.unlock();
|
||||
unlock_pending = false;
|
||||
thread_load_mutex_held = false;
|
||||
|
||||
if (!ignoring) {
|
||||
ResourceCache::lock.lock(); // Check and operations must happen atomically.
|
||||
bool pending_unlock = true;
|
||||
Ref<Resource> old_res = ResourceCache::get_ref(load_task.local_path);
|
||||
if (was_finished) {
|
||||
// If another thread already finished the entire load wait for it to complete
|
||||
// cache registration, then use their instance.
|
||||
while (!old_res.is_valid()) {
|
||||
ResourceCache::lock.unlock();
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
ResourceCache::lock.lock();
|
||||
old_res = ResourceCache::get_ref(load_task.local_path);
|
||||
}
|
||||
}
|
||||
if (old_res.is_valid()) {
|
||||
if (old_res != load_task.resource) {
|
||||
// Resource can already exists at this point for two reasons:
|
||||
|
|
@ -476,7 +626,7 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
|
|||
load_task.progress = 1.0;
|
||||
|
||||
thread_load_mutex.unlock();
|
||||
unlock_pending = false;
|
||||
thread_load_mutex_held = false;
|
||||
|
||||
if (_loaded_callback) {
|
||||
_loaded_callback(load_task.resource, load_task.local_path);
|
||||
|
|
@ -484,22 +634,56 @@ void ResourceLoader::_run_load_task(void *p_userdata) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!thread_load_mutex_held) {
|
||||
thread_load_mutex.lock();
|
||||
}
|
||||
|
||||
if (cleaning_tasks) {
|
||||
// If we are cleaning don't wake up yielders here.
|
||||
// And don't unreference the load token, it will get destroyed
|
||||
// with the task later.
|
||||
load_task.status = THREAD_LOAD_FAILED;
|
||||
thread_load_mutex.unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
if (load_task.error != OK) {
|
||||
load_task.status = THREAD_LOAD_FAILED;
|
||||
} else {
|
||||
load_task.status = THREAD_LOAD_LOADED;
|
||||
}
|
||||
|
||||
if (load_task.cond_var && load_task.need_wait) {
|
||||
load_task.cond_var->notify_all();
|
||||
}
|
||||
load_task.need_wait = false;
|
||||
|
||||
if (!thread_waiting_on_backup.is_empty()) {
|
||||
thread_waiting_on[thread_index] = thread_waiting_on_backup;
|
||||
}
|
||||
|
||||
for (int tid : yielders) {
|
||||
// Thundering herd, but not really an issue in practice. The
|
||||
// number of threads in the WorkerThreadPool is bound and
|
||||
// low.
|
||||
WorkerThreadPool::get_singleton()->notify_yield_over(tid);
|
||||
}
|
||||
|
||||
thread_load_mutex.unlock();
|
||||
|
||||
// It's safe now to let the task go in case no one else was grabbing the token.
|
||||
load_task.load_token->unreference();
|
||||
|
||||
if (unlock_pending) {
|
||||
thread_load_mutex.unlock();
|
||||
}
|
||||
|
||||
if (load_nesting == 0) {
|
||||
if (own_mq_override) {
|
||||
MessageQueue::set_thread_singleton_override(nullptr);
|
||||
memdelete(own_mq_override);
|
||||
}
|
||||
DEV_ASSERT(load_paths_stack.is_empty());
|
||||
}
|
||||
|
||||
curr_load_task = curr_load_task_backup;
|
||||
|
||||
print_verbose(vformat("Completed load for: '%s' remapped '%s' at thread %d", load_task.local_path, remapped_path, thread_index));
|
||||
}
|
||||
|
||||
String ResourceLoader::_validate_local_path(const String &p_path) {
|
||||
|
|
@ -513,7 +697,7 @@ String ResourceLoader::_validate_local_path(const String &p_path) {
|
|||
}
|
||||
}
|
||||
|
||||
Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) {
|
||||
Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, CacheMode p_cache_mode) {
|
||||
Ref<ResourceLoader::LoadToken> token = _load_start(p_path, p_type_hint, p_use_sub_threads ? LOAD_THREAD_DISTRIBUTE : LOAD_THREAD_SPAWN_SINGLE, p_cache_mode, true);
|
||||
return token.is_valid() ? OK : FAILED;
|
||||
}
|
||||
|
|
@ -538,7 +722,7 @@ void ResourceLoader::_load_threaded_request_setup_user_token(LoadToken *p_token,
|
|||
print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size()));
|
||||
}
|
||||
|
||||
Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) {
|
||||
Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, CacheMode p_cache_mode, Error *r_error) {
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
}
|
||||
|
|
@ -563,11 +747,11 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
|
|||
return res;
|
||||
}
|
||||
|
||||
Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode, bool p_for_user) {
|
||||
Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, CacheMode p_cache_mode, bool p_for_user) {
|
||||
String local_path = _validate_local_path(p_path);
|
||||
ERR_FAIL_COND_V(local_path.is_empty(), Ref<ResourceLoader::LoadToken>());
|
||||
|
||||
bool ignoring_cache = p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE || p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP;
|
||||
bool ignoring_cache = p_cache_mode == CACHE_MODE_IGNORE || p_cache_mode == CACHE_MODE_IGNORE_DEEP;
|
||||
|
||||
Ref<LoadToken> load_token;
|
||||
bool must_not_register = false;
|
||||
|
|
@ -613,7 +797,7 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
|
|||
load_task.type_hint = p_type_hint;
|
||||
load_task.cache_mode = p_cache_mode;
|
||||
load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE;
|
||||
if (p_cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE) {
|
||||
if (p_cache_mode == CACHE_MODE_REUSE) {
|
||||
Ref<Resource> existing = ResourceCache::get_ref(local_path);
|
||||
if (existing.is_valid()) {
|
||||
//referencing is fine
|
||||
|
|
@ -626,6 +810,12 @@ Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path,
|
|||
}
|
||||
}
|
||||
|
||||
// Task hierarchy
|
||||
if (curr_load_task) {
|
||||
load_task.parent_task = curr_load_task;
|
||||
curr_load_task->sub_tasks.insert(load_task.local_path);
|
||||
}
|
||||
|
||||
// If we want to ignore cache, but there's another task loading it, we can't add this one to the map.
|
||||
must_not_register = ignoring_cache && thread_load_tasks.has(local_path);
|
||||
if (must_not_register) {
|
||||
|
|
@ -781,6 +971,10 @@ Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_e
|
|||
thread_load_lock.temp_unlock();
|
||||
bool exit = !_ensure_load_progress();
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
if (MessageQueue::get_singleton()) {
|
||||
MessageQueue::get_singleton()->flush();
|
||||
}
|
||||
|
||||
thread_load_lock.temp_relock();
|
||||
if (exit) {
|
||||
break;
|
||||
|
|
@ -815,6 +1009,22 @@ void ResourceLoader::set_is_import_thread(bool p_import_thread) {
|
|||
import_thread = p_import_thread;
|
||||
}
|
||||
|
||||
void ResourceLoader::notify_load_error(const String &p_err) {
|
||||
if (err_notify) {
|
||||
MessageQueue::get_main_singleton()->push_callable(callable_mp_static(err_notify).bind(p_err));
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceLoader::notify_dependency_error(const String &p_path, const String &p_dependency, const String &p_type) {
|
||||
if (dep_err_notify) {
|
||||
if (Thread::get_caller_id() == Thread::get_main_id()) {
|
||||
dep_err_notify(p_path, p_dependency, p_type);
|
||||
} else {
|
||||
MessageQueue::get_main_singleton()->push_callable(callable_mp_static(dep_err_notify).bind(p_path, p_dependency, p_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock) {
|
||||
if (r_error) {
|
||||
*r_error = OK;
|
||||
|
|
@ -851,20 +1061,10 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
|
|||
// Loading thread is in the worker pool.
|
||||
p_thread_load_lock.temp_unlock();
|
||||
|
||||
PREPARE_FOR_WTP_WAIT
|
||||
Error wait_err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id);
|
||||
RESTORE_AFTER_WTP_WAIT
|
||||
|
||||
DEV_ASSERT(!wait_err || wait_err == ERR_BUSY);
|
||||
if (wait_err == ERR_BUSY) {
|
||||
// The WorkerThreadPool has reported that the current task wants to await on an older one.
|
||||
// That't not allowed for safety, to avoid deadlocks. Fortunately, though, in the context of
|
||||
// resource loading that means that the task to wait for can be restarted here to break the
|
||||
// cycle, with as much recursion into this process as needed.
|
||||
// When the stack is eventually unrolled, the original load will have been notified to go on.
|
||||
load_task.load_token->reference();
|
||||
_run_load_task(&load_task);
|
||||
}
|
||||
// The wtp won't let us wait on tasks that are older than us. But ResourceLoader has its own
|
||||
// deadlock detection and prevention in _run_load_task(), rely on that instead.
|
||||
load_task.load_token->reference();
|
||||
_run_load_task(&load_task);
|
||||
|
||||
p_thread_load_lock.temp_relock();
|
||||
load_task.awaited = true;
|
||||
|
|
@ -904,21 +1104,24 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
|
|||
load_task_ptr = &load_task;
|
||||
}
|
||||
|
||||
p_thread_load_lock.temp_unlock();
|
||||
|
||||
Ref<Resource> resource = load_task_ptr->resource;
|
||||
if (r_error) {
|
||||
*r_error = load_task_ptr->error;
|
||||
}
|
||||
|
||||
if (resource.is_valid()) {
|
||||
if (curr_load_task) {
|
||||
// A task awaiting another => Let the awaiter accumulate the resource changed connections.
|
||||
DEV_ASSERT(curr_load_task != load_task_ptr);
|
||||
for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) {
|
||||
curr_load_task->resource_changed_connections.push_back(rcc);
|
||||
if (load_task_ptr->parent_task) {
|
||||
if (!load_task_ptr->connections_propagated) {
|
||||
// A task awaiting another => Let the awaiter accumulate the resource changed connections.
|
||||
DEV_ASSERT(load_task_ptr->parent_task != load_task_ptr);
|
||||
for (const ThreadLoadTask::ResourceChangedConnection &rcc : load_task_ptr->resource_changed_connections) {
|
||||
load_task_ptr->parent_task->resource_changed_connections.push_back(rcc);
|
||||
}
|
||||
load_task_ptr->connections_propagated = true;
|
||||
}
|
||||
} else {
|
||||
p_thread_load_lock.temp_unlock();
|
||||
|
||||
// A leaf task being awaited => Propagate the resource changed connections.
|
||||
if (Thread::is_main_thread()) {
|
||||
// On the main thread it's safe to migrate the connections to the standard signal mechanism.
|
||||
|
|
@ -942,11 +1145,11 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
p_thread_load_lock.temp_relock();
|
||||
}
|
||||
}
|
||||
|
||||
p_thread_load_lock.temp_relock();
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
|
@ -977,6 +1180,7 @@ void ResourceLoader::resource_changed_connect(Resource *p_source, const Callable
|
|||
rcc.callable = p_callable;
|
||||
rcc.flags = p_flags;
|
||||
curr_load_task->resource_changed_connections.push_back(rcc);
|
||||
curr_load_task->resource_dependencies.push_back(p_source);
|
||||
}
|
||||
|
||||
void ResourceLoader::resource_changed_disconnect(Resource *p_source, const Callable &p_callable) {
|
||||
|
|
@ -987,6 +1191,7 @@ void ResourceLoader::resource_changed_disconnect(Resource *p_source, const Calla
|
|||
for (uint32_t i = 0; i < curr_load_task->resource_changed_connections.size(); ++i) {
|
||||
const ThreadLoadTask::ResourceChangedConnection &rcc = curr_load_task->resource_changed_connections[i];
|
||||
if (unlikely(rcc.source == p_source && rcc.callable == p_callable)) {
|
||||
curr_load_task->resource_dependencies.erase(p_source);
|
||||
curr_load_task->resource_changed_connections.remove_at_unordered(i);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1046,8 +1251,7 @@ bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
|
|||
return true; // If cached, it probably exists
|
||||
}
|
||||
|
||||
bool xl_remapped = false;
|
||||
String path = _path_remap(local_path, &xl_remapped);
|
||||
String path = _path_remap(local_path);
|
||||
|
||||
// Try all loaders and pick the first match for the type hint
|
||||
for (int i = 0; i < loader_count; i++) {
|
||||
|
|
@ -1389,6 +1593,9 @@ void ResourceLoader::clear_thread_load_tasks() {
|
|||
|
||||
while (true) {
|
||||
bool none_running = true;
|
||||
for (int tid : yielders) {
|
||||
WorkerThreadPool::get_singleton()->notify_yield_over(tid);
|
||||
}
|
||||
if (thread_load_tasks.size()) {
|
||||
for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) {
|
||||
if (E.value.status == THREAD_LOAD_IN_PROGRESS) {
|
||||
|
|
@ -1403,8 +1610,15 @@ void ResourceLoader::clear_thread_load_tasks() {
|
|||
if (none_running) {
|
||||
break;
|
||||
}
|
||||
|
||||
thread_load_lock.temp_unlock();
|
||||
|
||||
if (MessageQueue::get_singleton()) {
|
||||
MessageQueue::get_singleton()->flush();
|
||||
}
|
||||
|
||||
OS::get_singleton()->delay_usec(1000);
|
||||
|
||||
thread_load_lock.temp_relock();
|
||||
}
|
||||
|
||||
|
|
@ -1418,6 +1632,8 @@ void ResourceLoader::clear_thread_load_tasks() {
|
|||
}
|
||||
|
||||
thread_load_tasks.clear();
|
||||
thread_waiting_on.clear();
|
||||
// yielders is already guaranteed to be empty now
|
||||
|
||||
cleaning_tasks = false;
|
||||
}
|
||||
|
|
@ -1568,7 +1784,6 @@ bool ResourceLoader::timestamp_on_load = false;
|
|||
|
||||
thread_local bool ResourceLoader::import_thread = false;
|
||||
thread_local int ResourceLoader::load_nesting = 0;
|
||||
thread_local Vector<String> ResourceLoader::load_paths_stack;
|
||||
thread_local HashMap<int, HashMap<String, Ref<Resource>>> ResourceLoader::res_ref_overrides;
|
||||
thread_local ResourceLoader::ThreadLoadTask *ResourceLoader::curr_load_task = nullptr;
|
||||
|
||||
|
|
@ -1580,6 +1795,9 @@ template <>
|
|||
thread_local SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::TLSData SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::tls_data(_get_res_loader_mutex());
|
||||
SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> ResourceLoader::thread_load_mutex;
|
||||
HashMap<String, ResourceLoader::ThreadLoadTask> ResourceLoader::thread_load_tasks;
|
||||
HashMap<int, String> ResourceLoader::thread_waiting_on;
|
||||
LocalVector<int> ResourceLoader::yielders;
|
||||
|
||||
bool ResourceLoader::cleaning_tasks = false;
|
||||
|
||||
HashMap<String, ResourceLoader::LoadToken *> ResourceLoader::user_load_tokens;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue