1/**************************************************************************/
2/* resource_loader.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "resource_loader.h"
32
33#include "core/config/project_settings.h"
34#include "core/io/file_access.h"
35#include "core/io/resource_importer.h"
36#include "core/object/script_language.h"
37#include "core/os/condition_variable.h"
38#include "core/os/os.h"
39#include "core/string/print_string.h"
40#include "core/string/translation.h"
41#include "core/variant/variant_parser.h"
42
43#ifdef DEBUG_LOAD_THREADED
44#define print_lt(m_text) print_line(m_text)
45#else
46#define print_lt(m_text)
47#endif
48
49Ref<ResourceFormatLoader> ResourceLoader::loader[ResourceLoader::MAX_LOADERS];
50
51int ResourceLoader::loader_count = 0;
52
53bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_for_type) const {
54 bool ret = false;
55 if (GDVIRTUAL_CALL(_recognize_path, p_path, p_for_type, ret)) {
56 return ret;
57 }
58
59 String extension = p_path.get_extension();
60
61 List<String> extensions;
62 if (p_for_type.is_empty()) {
63 get_recognized_extensions(&extensions);
64 } else {
65 get_recognized_extensions_for_type(p_for_type, &extensions);
66 }
67
68 for (const String &E : extensions) {
69 if (E.nocasecmp_to(extension) == 0) {
70 return true;
71 }
72 }
73
74 return false;
75}
76
77bool ResourceFormatLoader::handles_type(const String &p_type) const {
78 bool success = false;
79 GDVIRTUAL_CALL(_handles_type, p_type, success);
80 return success;
81}
82
83void ResourceFormatLoader::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
84 Vector<String> ret;
85 if (GDVIRTUAL_CALL(_get_classes_used, p_path, ret)) {
86 for (int i = 0; i < ret.size(); i++) {
87 r_classes->insert(ret[i]);
88 }
89 return;
90 }
91
92 String res = get_resource_type(p_path);
93 if (!res.is_empty()) {
94 r_classes->insert(res);
95 }
96}
97
98String ResourceFormatLoader::get_resource_type(const String &p_path) const {
99 String ret;
100 GDVIRTUAL_CALL(_get_resource_type, p_path, ret);
101 return ret;
102}
103
104String ResourceFormatLoader::get_resource_script_class(const String &p_path) const {
105 String ret;
106 GDVIRTUAL_CALL(_get_resource_script_class, p_path, ret);
107 return ret;
108}
109
110ResourceUID::ID ResourceFormatLoader::get_resource_uid(const String &p_path) const {
111 int64_t uid = ResourceUID::INVALID_ID;
112 GDVIRTUAL_CALL(_get_resource_uid, p_path, uid);
113 return uid;
114}
115
116void ResourceFormatLoader::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
117 if (p_type.is_empty() || handles_type(p_type)) {
118 get_recognized_extensions(p_extensions);
119 }
120}
121
122void ResourceLoader::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) {
123 for (int i = 0; i < loader_count; i++) {
124 loader[i]->get_recognized_extensions_for_type(p_type, p_extensions);
125 }
126}
127
128bool ResourceFormatLoader::exists(const String &p_path) const {
129 bool success = false;
130 if (GDVIRTUAL_CALL(_exists, p_path, success)) {
131 return success;
132 }
133 return FileAccess::exists(p_path); // By default just check file.
134}
135
136void ResourceFormatLoader::get_recognized_extensions(List<String> *p_extensions) const {
137 PackedStringArray exts;
138 if (GDVIRTUAL_CALL(_get_recognized_extensions, exts)) {
139 const String *r = exts.ptr();
140 for (int i = 0; i < exts.size(); ++i) {
141 p_extensions->push_back(r[i]);
142 }
143 }
144}
145
146Ref<Resource> ResourceFormatLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
147 Variant res;
148 if (GDVIRTUAL_CALL(_load, p_path, p_original_path, p_use_sub_threads, p_cache_mode, res)) {
149 if (res.get_type() == Variant::INT) { // Error code, abort.
150 if (r_error) {
151 *r_error = (Error)res.operator int64_t();
152 }
153 return Ref<Resource>();
154 } else { // Success, pass on result.
155 if (r_error) {
156 *r_error = OK;
157 }
158 return res;
159 }
160 }
161
162 ERR_FAIL_V_MSG(Ref<Resource>(), "Failed to load resource '" + p_path + "'. ResourceFormatLoader::load was not implemented for this resource type.");
163}
164
165void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
166 PackedStringArray deps;
167 if (GDVIRTUAL_CALL(_get_dependencies, p_path, p_add_types, deps)) {
168 const String *r = deps.ptr();
169 for (int i = 0; i < deps.size(); ++i) {
170 p_dependencies->push_back(r[i]);
171 }
172 }
173}
174
175Error ResourceFormatLoader::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) {
176 Dictionary deps_dict;
177 for (KeyValue<String, String> E : p_map) {
178 deps_dict[E.key] = E.value;
179 }
180
181 Error err = OK;
182 GDVIRTUAL_CALL(_rename_dependencies, p_path, deps_dict, err);
183 return err;
184}
185
186void ResourceFormatLoader::_bind_methods() {
187 BIND_ENUM_CONSTANT(CACHE_MODE_IGNORE);
188 BIND_ENUM_CONSTANT(CACHE_MODE_REUSE);
189 BIND_ENUM_CONSTANT(CACHE_MODE_REPLACE);
190
191 GDVIRTUAL_BIND(_get_recognized_extensions);
192 GDVIRTUAL_BIND(_recognize_path, "path", "type");
193 GDVIRTUAL_BIND(_handles_type, "type");
194 GDVIRTUAL_BIND(_get_resource_type, "path");
195 GDVIRTUAL_BIND(_get_resource_script_class, "path");
196 GDVIRTUAL_BIND(_get_resource_uid, "path");
197 GDVIRTUAL_BIND(_get_dependencies, "path", "add_types");
198 GDVIRTUAL_BIND(_rename_dependencies, "path", "renames");
199 GDVIRTUAL_BIND(_exists, "path");
200 GDVIRTUAL_BIND(_get_classes_used, "path");
201 GDVIRTUAL_BIND(_load, "path", "original_path", "use_sub_threads", "cache_mode");
202}
203
204///////////////////////////////////
205
206// This should be robust enough to be called redundantly without issues.
207void ResourceLoader::LoadToken::clear() {
208 thread_load_mutex.lock();
209
210 WorkerThreadPool::TaskID task_to_await = 0;
211
212 if (!local_path.is_empty()) { // Empty is used for the special case where the load task is not registered.
213 DEV_ASSERT(thread_load_tasks.has(local_path));
214 ThreadLoadTask &load_task = thread_load_tasks[local_path];
215 if (!load_task.awaited) {
216 task_to_await = load_task.task_id;
217 load_task.awaited = true;
218 }
219 thread_load_tasks.erase(local_path);
220 local_path.clear();
221 }
222
223 if (!user_path.is_empty()) {
224 DEV_ASSERT(user_load_tokens.has(user_path));
225 user_load_tokens.erase(user_path);
226 user_path.clear();
227 }
228
229 thread_load_mutex.unlock();
230
231 // If task is unused, await it here, locally, now the token data is consistent.
232 if (task_to_await) {
233 WorkerThreadPool::get_singleton()->wait_for_task_completion(task_to_await);
234 }
235}
236
237ResourceLoader::LoadToken::~LoadToken() {
238 clear();
239}
240
241Ref<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) {
242 load_nesting++;
243 if (load_paths_stack->size()) {
244 thread_load_mutex.lock();
245 HashMap<String, ThreadLoadTask>::Iterator E = thread_load_tasks.find(load_paths_stack->get(load_paths_stack->size() - 1));
246 if (E) {
247 E->value.sub_tasks.insert(p_path);
248 }
249 thread_load_mutex.unlock();
250 }
251 load_paths_stack->push_back(p_path);
252
253 // Try all loaders and pick the first match for the type hint
254 bool found = false;
255 Ref<Resource> res;
256 for (int i = 0; i < loader_count; i++) {
257 if (!loader[i]->recognize_path(p_path, p_type_hint)) {
258 continue;
259 }
260 found = true;
261 res = loader[i]->load(p_path, !p_original_path.is_empty() ? p_original_path : p_path, r_error, p_use_sub_threads, r_progress, p_cache_mode);
262 if (!res.is_null()) {
263 break;
264 }
265 }
266
267 load_paths_stack->resize(load_paths_stack->size() - 1);
268 load_nesting--;
269
270 if (!res.is_null()) {
271 return res;
272 }
273
274 ERR_FAIL_COND_V_MSG(found, Ref<Resource>(),
275 vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path));
276
277#ifdef TOOLS_ENABLED
278 Ref<FileAccess> file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES);
279 ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), Ref<Resource>(), vformat("Resource file not found: %s (expected type: %s)", p_path, p_type_hint));
280#endif
281
282 ERR_FAIL_V_MSG(Ref<Resource>(), vformat("No loader found for resource: %s (expected type: %s)", p_path, p_type_hint));
283}
284
285void ResourceLoader::_thread_load_function(void *p_userdata) {
286 ThreadLoadTask &load_task = *(ThreadLoadTask *)p_userdata;
287
288 thread_load_mutex.lock();
289 caller_task_id = load_task.task_id;
290 if (cleaning_tasks) {
291 load_task.status = THREAD_LOAD_FAILED;
292 thread_load_mutex.unlock();
293 return;
294 }
295 thread_load_mutex.unlock();
296
297 // Thread-safe either if it's the current thread or a brand new one.
298 CallQueue *mq_override = nullptr;
299 if (load_nesting == 0) {
300 load_paths_stack = memnew(Vector<String>);
301
302 if (!load_task.dependent_path.is_empty()) {
303 load_paths_stack->push_back(load_task.dependent_path);
304 }
305 if (!Thread::is_main_thread()) {
306 mq_override = memnew(CallQueue);
307 MessageQueue::set_thread_singleton_override(mq_override);
308 set_current_thread_safe_for_nodes(true);
309 }
310 } else {
311 DEV_ASSERT(load_task.dependent_path.is_empty());
312 }
313 // --
314
315 if (!Thread::is_main_thread()) {
316 set_current_thread_safe_for_nodes(true);
317 }
318
319 Ref<Resource> res = _load(load_task.remapped_path, load_task.remapped_path != load_task.local_path ? load_task.local_path : String(), load_task.type_hint, load_task.cache_mode, &load_task.error, load_task.use_sub_threads, &load_task.progress);
320 if (mq_override) {
321 mq_override->flush();
322 }
323
324 thread_load_mutex.lock();
325
326 load_task.resource = res;
327
328 load_task.progress = 1.0; //it was fully loaded at this point, so force progress to 1.0
329 if (load_task.error != OK) {
330 load_task.status = THREAD_LOAD_FAILED;
331 } else {
332 load_task.status = THREAD_LOAD_LOADED;
333 }
334
335 if (load_task.cond_var) {
336 load_task.cond_var->notify_all();
337 memdelete(load_task.cond_var);
338 load_task.cond_var = nullptr;
339 }
340
341 if (load_task.resource.is_valid()) {
342 if (load_task.cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
343 load_task.resource->set_path(load_task.local_path);
344 }
345
346 if (load_task.xl_remapped) {
347 load_task.resource->set_as_translation_remapped(true);
348 }
349
350#ifdef TOOLS_ENABLED
351 load_task.resource->set_edited(false);
352 if (timestamp_on_load) {
353 uint64_t mt = FileAccess::get_modified_time(load_task.remapped_path);
354 //printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt);
355 load_task.resource->set_last_modified_time(mt);
356 }
357#endif
358
359 if (_loaded_callback) {
360 _loaded_callback(load_task.resource, load_task.local_path);
361 }
362 }
363
364 thread_load_mutex.unlock();
365
366 if (load_nesting == 0) {
367 if (mq_override) {
368 memdelete(mq_override);
369 }
370 memdelete(load_paths_stack);
371 }
372}
373
374static String _validate_local_path(const String &p_path) {
375 ResourceUID::ID uid = ResourceUID::get_singleton()->text_to_id(p_path);
376 if (uid != ResourceUID::INVALID_ID) {
377 return ResourceUID::get_singleton()->get_id_path(uid);
378 } else if (p_path.is_relative_path()) {
379 return "res://" + p_path;
380 } else {
381 return ProjectSettings::get_singleton()->localize_path(p_path);
382 }
383}
384
385Error ResourceLoader::load_threaded_request(const String &p_path, const String &p_type_hint, bool p_use_sub_threads, ResourceFormatLoader::CacheMode p_cache_mode) {
386 thread_load_mutex.lock();
387 if (user_load_tokens.has(p_path)) {
388 print_verbose("load_threaded_request(): Another threaded load for resource path '" + p_path + "' has been initiated. Not an error.");
389 user_load_tokens[p_path]->reference(); // Additional request.
390 thread_load_mutex.unlock();
391 return OK;
392 }
393 user_load_tokens[p_path] = nullptr;
394 thread_load_mutex.unlock();
395
396 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);
397 if (token.is_valid()) {
398 thread_load_mutex.lock();
399 token->user_path = p_path;
400 token->reference(); // First request.
401 user_load_tokens[p_path] = token.ptr();
402 print_lt("REQUEST: user load tokens: " + itos(user_load_tokens.size()));
403 thread_load_mutex.unlock();
404 return OK;
405 } else {
406 return FAILED;
407 }
408}
409
410Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hint, ResourceFormatLoader::CacheMode p_cache_mode, Error *r_error) {
411 if (r_error) {
412 *r_error = OK;
413 }
414
415 Ref<LoadToken> load_token = _load_start(p_path, p_type_hint, LOAD_THREAD_FROM_CURRENT, p_cache_mode);
416 if (!load_token.is_valid()) {
417 if (r_error) {
418 *r_error = FAILED;
419 }
420 return Ref<Resource>();
421 }
422
423 Ref<Resource> res = _load_complete(*load_token.ptr(), r_error);
424 return res;
425}
426
427Ref<ResourceLoader::LoadToken> ResourceLoader::_load_start(const String &p_path, const String &p_type_hint, LoadThreadMode p_thread_mode, ResourceFormatLoader::CacheMode p_cache_mode) {
428 String local_path = _validate_local_path(p_path);
429
430 Ref<LoadToken> load_token;
431 bool must_not_register = false;
432 ThreadLoadTask unregistered_load_task; // Once set, must be valid up to the call to do the load.
433 ThreadLoadTask *load_task_ptr = nullptr;
434 bool run_on_current_thread = false;
435 {
436 MutexLock thread_load_lock(thread_load_mutex);
437
438 if (thread_load_tasks.has(local_path)) {
439 load_token = Ref<LoadToken>(thread_load_tasks[local_path].load_token);
440 if (!load_token.is_valid()) {
441 // The token is dying (reached 0 on another thread).
442 // Ensure it's killed now so the path can be safely reused right away.
443 thread_load_tasks[local_path].load_token->clear();
444 } else {
445 if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
446 return load_token;
447 }
448 }
449 }
450
451 load_token.instantiate();
452 load_token->local_path = local_path;
453
454 //create load task
455 {
456 ThreadLoadTask load_task;
457
458 load_task.remapped_path = _path_remap(local_path, &load_task.xl_remapped);
459 load_task.load_token = load_token.ptr();
460 load_task.local_path = local_path;
461 load_task.type_hint = p_type_hint;
462 load_task.cache_mode = p_cache_mode;
463 load_task.use_sub_threads = p_thread_mode == LOAD_THREAD_DISTRIBUTE;
464 if (p_cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE) {
465 Ref<Resource> existing = ResourceCache::get_ref(local_path);
466 if (existing.is_valid()) {
467 //referencing is fine
468 load_task.resource = existing;
469 load_task.status = THREAD_LOAD_LOADED;
470 load_task.progress = 1.0;
471 thread_load_tasks[local_path] = load_task;
472 return load_token;
473 }
474 }
475
476 // If we want to ignore cache, but there's another task loading it, we can't add this one to the map and we also have to finish unconditionally synchronously.
477 must_not_register = thread_load_tasks.has(local_path) && p_cache_mode == ResourceFormatLoader::CACHE_MODE_IGNORE;
478 if (must_not_register) {
479 load_token->local_path.clear();
480 unregistered_load_task = load_task;
481 } else {
482 thread_load_tasks[local_path] = load_task;
483 }
484
485 load_task_ptr = must_not_register ? &unregistered_load_task : &thread_load_tasks[local_path];
486 }
487
488 run_on_current_thread = must_not_register || p_thread_mode == LOAD_THREAD_FROM_CURRENT;
489
490 if (run_on_current_thread) {
491 load_task_ptr->thread_id = Thread::get_caller_id();
492 } else {
493 load_task_ptr->task_id = WorkerThreadPool::get_singleton()->add_native_task(&ResourceLoader::_thread_load_function, load_task_ptr);
494 }
495 }
496
497 if (run_on_current_thread) {
498 _thread_load_function(load_task_ptr);
499 if (must_not_register) {
500 load_token->res_if_unregistered = load_task_ptr->resource;
501 }
502 }
503
504 return load_token;
505}
506
507float ResourceLoader::_dependency_get_progress(const String &p_path) {
508 if (thread_load_tasks.has(p_path)) {
509 ThreadLoadTask &load_task = thread_load_tasks[p_path];
510 int dep_count = load_task.sub_tasks.size();
511 if (dep_count > 0) {
512 float dep_progress = 0;
513 for (const String &E : load_task.sub_tasks) {
514 dep_progress += _dependency_get_progress(E);
515 }
516 dep_progress /= float(dep_count);
517 dep_progress *= 0.5;
518 dep_progress += load_task.progress * 0.5;
519 return dep_progress;
520 } else {
521 return load_task.progress;
522 }
523
524 } else {
525 return 1.0; //assume finished loading it so it no longer exists
526 }
527}
528
529ResourceLoader::ThreadLoadStatus ResourceLoader::load_threaded_get_status(const String &p_path, float *r_progress) {
530 MutexLock thread_load_lock(thread_load_mutex);
531
532 if (!user_load_tokens.has(p_path)) {
533 print_verbose("load_threaded_get_status(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected.");
534 return THREAD_LOAD_INVALID_RESOURCE;
535 }
536
537 String local_path = _validate_local_path(p_path);
538 if (!thread_load_tasks.has(local_path)) {
539#ifdef DEV_ENABLED
540 CRASH_NOW();
541#endif
542 // On non-dev, be defensive and at least avoid crashing (at this point at least).
543 return THREAD_LOAD_INVALID_RESOURCE;
544 }
545
546 ThreadLoadTask &load_task = thread_load_tasks[local_path];
547 ThreadLoadStatus status;
548 status = load_task.status;
549 if (r_progress) {
550 *r_progress = _dependency_get_progress(local_path);
551 }
552
553 return status;
554}
555
556Ref<Resource> ResourceLoader::load_threaded_get(const String &p_path, Error *r_error) {
557 if (r_error) {
558 *r_error = OK;
559 }
560
561 Ref<Resource> res;
562 {
563 MutexLock thread_load_lock(thread_load_mutex);
564
565 if (!user_load_tokens.has(p_path)) {
566 print_verbose("load_threaded_get(): No threaded load for resource path '" + p_path + "' has been initiated or its result has already been collected.");
567 if (r_error) {
568 *r_error = ERR_INVALID_PARAMETER;
569 }
570 return Ref<Resource>();
571 }
572
573 LoadToken *load_token = user_load_tokens[p_path];
574 if (!load_token) {
575 // This happens if requested from one thread and rapidly querying from another.
576 if (r_error) {
577 *r_error = ERR_BUSY;
578 }
579 return Ref<Resource>();
580 }
581 res = _load_complete_inner(*load_token, r_error, thread_load_lock);
582 if (load_token->unreference()) {
583 memdelete(load_token);
584 }
585 }
586
587 print_lt("GET: user load tokens: " + itos(user_load_tokens.size()));
588
589 return res;
590}
591
592Ref<Resource> ResourceLoader::_load_complete(LoadToken &p_load_token, Error *r_error) {
593 MutexLock thread_load_lock(thread_load_mutex);
594 return _load_complete_inner(p_load_token, r_error, thread_load_lock);
595}
596
597Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Error *r_error, MutexLock<SafeBinaryMutex<BINARY_MUTEX_TAG>> &p_thread_load_lock) {
598 if (r_error) {
599 *r_error = OK;
600 }
601
602 if (!p_load_token.local_path.is_empty()) {
603 if (!thread_load_tasks.has(p_load_token.local_path)) {
604#ifdef DEV_ENABLED
605 CRASH_NOW();
606#endif
607 // On non-dev, be defensive and at least avoid crashing (at this point at least).
608 if (r_error) {
609 *r_error = ERR_BUG;
610 }
611 return Ref<Resource>();
612 }
613
614 ThreadLoadTask &load_task = thread_load_tasks[p_load_token.local_path];
615
616 if (load_task.status == THREAD_LOAD_IN_PROGRESS) {
617 DEV_ASSERT((load_task.task_id == 0) != (load_task.thread_id == 0));
618
619 if ((load_task.task_id != 0 && load_task.task_id == caller_task_id) ||
620 (load_task.thread_id != 0 && load_task.thread_id == Thread::get_caller_id())) {
621 // Load is in progress, but it's precisely this thread the one in charge.
622 // That means this is a cyclic load.
623 if (r_error) {
624 *r_error = ERR_BUSY;
625 }
626 return Ref<Resource>();
627 }
628
629 if (load_task.task_id != 0) {
630 // Loading thread is in the worker pool.
631 load_task.awaited = true;
632 thread_load_mutex.unlock();
633 Error err = WorkerThreadPool::get_singleton()->wait_for_task_completion(load_task.task_id);
634 if (err == ERR_BUSY) {
635 // The WorkerThreadPool has scheduled tasks in a way that the current load depends on
636 // another one in a lower stack frame. Restart such load here. When the stack is eventually
637 // unrolled, the original load will have been notified to go on.
638#ifdef DEV_ENABLED
639 print_verbose("ResourceLoader: Load task happened to wait on another one deep in the call stack. Attempting to avoid deadlock by re-issuing the load now.");
640#endif
641 // CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's
642 // an ongoing load for that resource and wait for it again. This value forces a new load.
643 Ref<ResourceLoader::LoadToken> token = _load_start(load_task.local_path, load_task.type_hint, LOAD_THREAD_DISTRIBUTE, ResourceFormatLoader::CACHE_MODE_IGNORE);
644 Ref<Resource> resource = _load_complete(*token.ptr(), &err);
645 if (r_error) {
646 *r_error = err;
647 }
648 thread_load_mutex.lock();
649 return resource;
650 } else {
651 DEV_ASSERT(err == OK);
652 thread_load_mutex.lock();
653 }
654 } else {
655 // Loading thread is main or user thread.
656 if (!load_task.cond_var) {
657 load_task.cond_var = memnew(ConditionVariable);
658 }
659 do {
660 load_task.cond_var->wait(p_thread_load_lock);
661 DEV_ASSERT(thread_load_tasks.has(p_load_token.local_path) && p_load_token.get_reference_count());
662 } while (load_task.cond_var);
663 }
664 }
665
666 if (cleaning_tasks) {
667 load_task.resource = Ref<Resource>();
668 load_task.error = FAILED;
669 }
670
671 Ref<Resource> resource = load_task.resource;
672 if (r_error) {
673 *r_error = load_task.error;
674 }
675 return resource;
676 } else {
677 // Special case of an unregistered task.
678 // The resource should have been loaded by now.
679 Ref<Resource> resource = p_load_token.res_if_unregistered;
680 if (!resource.is_valid()) {
681 if (r_error) {
682 *r_error = FAILED;
683 }
684 }
685 return resource;
686 }
687}
688
689bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
690 String local_path = _validate_local_path(p_path);
691
692 if (ResourceCache::has(local_path)) {
693 return true; // If cached, it probably exists
694 }
695
696 bool xl_remapped = false;
697 String path = _path_remap(local_path, &xl_remapped);
698
699 // Try all loaders and pick the first match for the type hint
700 for (int i = 0; i < loader_count; i++) {
701 if (!loader[i]->recognize_path(path, p_type_hint)) {
702 continue;
703 }
704
705 if (loader[i]->exists(path)) {
706 return true;
707 }
708 }
709
710 return false;
711}
712
713void ResourceLoader::add_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader, bool p_at_front) {
714 ERR_FAIL_COND(p_format_loader.is_null());
715 ERR_FAIL_COND(loader_count >= MAX_LOADERS);
716
717 if (p_at_front) {
718 for (int i = loader_count; i > 0; i--) {
719 loader[i] = loader[i - 1];
720 }
721 loader[0] = p_format_loader;
722 loader_count++;
723 } else {
724 loader[loader_count++] = p_format_loader;
725 }
726}
727
728void ResourceLoader::remove_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader) {
729 ERR_FAIL_COND(p_format_loader.is_null());
730
731 // Find loader
732 int i = 0;
733 for (; i < loader_count; ++i) {
734 if (loader[i] == p_format_loader) {
735 break;
736 }
737 }
738
739 ERR_FAIL_COND(i >= loader_count); // Not found
740
741 // Shift next loaders up
742 for (; i < loader_count - 1; ++i) {
743 loader[i] = loader[i + 1];
744 }
745 loader[loader_count - 1].unref();
746 --loader_count;
747}
748
749int ResourceLoader::get_import_order(const String &p_path) {
750 String local_path = _path_remap(_validate_local_path(p_path));
751
752 for (int i = 0; i < loader_count; i++) {
753 if (!loader[i]->recognize_path(local_path)) {
754 continue;
755 }
756
757 return loader[i]->get_import_order(p_path);
758 }
759
760 return 0;
761}
762
763String ResourceLoader::get_import_group_file(const String &p_path) {
764 String local_path = _path_remap(_validate_local_path(p_path));
765
766 for (int i = 0; i < loader_count; i++) {
767 if (!loader[i]->recognize_path(local_path)) {
768 continue;
769 }
770
771 return loader[i]->get_import_group_file(p_path);
772 }
773
774 return String(); //not found
775}
776
777bool ResourceLoader::is_import_valid(const String &p_path) {
778 String local_path = _path_remap(_validate_local_path(p_path));
779
780 for (int i = 0; i < loader_count; i++) {
781 if (!loader[i]->recognize_path(local_path)) {
782 continue;
783 }
784
785 return loader[i]->is_import_valid(p_path);
786 }
787
788 return false; //not found
789}
790
791bool ResourceLoader::is_imported(const String &p_path) {
792 String local_path = _path_remap(_validate_local_path(p_path));
793
794 for (int i = 0; i < loader_count; i++) {
795 if (!loader[i]->recognize_path(local_path)) {
796 continue;
797 }
798
799 return loader[i]->is_imported(p_path);
800 }
801
802 return false; //not found
803}
804
805void ResourceLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
806 String local_path = _path_remap(_validate_local_path(p_path));
807
808 for (int i = 0; i < loader_count; i++) {
809 if (!loader[i]->recognize_path(local_path)) {
810 continue;
811 }
812
813 loader[i]->get_dependencies(local_path, p_dependencies, p_add_types);
814 }
815}
816
817Error ResourceLoader::rename_dependencies(const String &p_path, const HashMap<String, String> &p_map) {
818 String local_path = _path_remap(_validate_local_path(p_path));
819
820 for (int i = 0; i < loader_count; i++) {
821 if (!loader[i]->recognize_path(local_path)) {
822 continue;
823 }
824
825 return loader[i]->rename_dependencies(local_path, p_map);
826 }
827
828 return OK; // ??
829}
830
831void ResourceLoader::get_classes_used(const String &p_path, HashSet<StringName> *r_classes) {
832 String local_path = _validate_local_path(p_path);
833
834 for (int i = 0; i < loader_count; i++) {
835 if (!loader[i]->recognize_path(local_path)) {
836 continue;
837 }
838
839 return loader[i]->get_classes_used(p_path, r_classes);
840 }
841}
842
843String ResourceLoader::get_resource_type(const String &p_path) {
844 String local_path = _validate_local_path(p_path);
845
846 for (int i = 0; i < loader_count; i++) {
847 String result = loader[i]->get_resource_type(local_path);
848 if (!result.is_empty()) {
849 return result;
850 }
851 }
852
853 return "";
854}
855
856String ResourceLoader::get_resource_script_class(const String &p_path) {
857 String local_path = _validate_local_path(p_path);
858
859 for (int i = 0; i < loader_count; i++) {
860 String result = loader[i]->get_resource_script_class(local_path);
861 if (!result.is_empty()) {
862 return result;
863 }
864 }
865
866 return "";
867}
868
869ResourceUID::ID ResourceLoader::get_resource_uid(const String &p_path) {
870 String local_path = _validate_local_path(p_path);
871
872 for (int i = 0; i < loader_count; i++) {
873 ResourceUID::ID id = loader[i]->get_resource_uid(local_path);
874 if (id != ResourceUID::INVALID_ID) {
875 return id;
876 }
877 }
878
879 return ResourceUID::INVALID_ID;
880}
881
882String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_remapped) {
883 String new_path = p_path;
884
885 if (translation_remaps.has(p_path)) {
886 // translation_remaps has the following format:
887 // { "res://path.png": PackedStringArray( "res://path-ru.png:ru", "res://path-de.png:de" ) }
888
889 // To find the path of the remapped resource, we extract the locale name after
890 // the last ':' to match the project locale.
891
892 // An extra remap may still be necessary afterwards due to the text -> binary converter on export.
893
894 String locale = TranslationServer::get_singleton()->get_locale();
895 ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
896
897 Vector<String> &res_remaps = *translation_remaps.getptr(new_path);
898
899 int best_score = 0;
900 for (int i = 0; i < res_remaps.size(); i++) {
901 int split = res_remaps[i].rfind(":");
902 if (split == -1) {
903 continue;
904 }
905 String l = res_remaps[i].substr(split + 1).strip_edges();
906 int score = TranslationServer::get_singleton()->compare_locales(locale, l);
907 if (score > 0 && score >= best_score) {
908 new_path = res_remaps[i].left(split);
909 best_score = score;
910 if (score == 10) {
911 break; // Exact match, skip the rest.
912 }
913 }
914 }
915
916 if (r_translation_remapped) {
917 *r_translation_remapped = true;
918 }
919
920 // Fallback to p_path if new_path does not exist.
921 if (!FileAccess::exists(new_path)) {
922 WARN_PRINT(vformat("Translation remap '%s' does not exist. Falling back to '%s'.", new_path, p_path));
923 new_path = p_path;
924 }
925 }
926
927 if (path_remaps.has(new_path)) {
928 new_path = path_remaps[new_path];
929 } else {
930 // Try file remap.
931 Error err;
932 Ref<FileAccess> f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err);
933 if (f.is_valid()) {
934 VariantParser::StreamFile stream;
935 stream.f = f;
936
937 String assign;
938 Variant value;
939 VariantParser::Tag next_tag;
940
941 int lines = 0;
942 String error_text;
943 while (true) {
944 assign = Variant();
945 next_tag.fields.clear();
946 next_tag.name = String();
947
948 err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
949 if (err == ERR_FILE_EOF) {
950 break;
951 } else if (err != OK) {
952 ERR_PRINT("Parse error: " + p_path + ".remap:" + itos(lines) + " error: " + error_text + ".");
953 break;
954 }
955
956 if (assign == "path") {
957 new_path = value;
958 break;
959 } else if (next_tag.name != "remap") {
960 break;
961 }
962 }
963 }
964 }
965
966 return new_path;
967}
968
969String ResourceLoader::import_remap(const String &p_path) {
970 if (ResourceFormatImporter::get_singleton()->recognize_path(p_path)) {
971 return ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path);
972 }
973
974 return p_path;
975}
976
977String ResourceLoader::path_remap(const String &p_path) {
978 return _path_remap(p_path);
979}
980
981void ResourceLoader::reload_translation_remaps() {
982 ResourceCache::lock.lock();
983
984 List<Resource *> to_reload;
985 SelfList<Resource> *E = remapped_list.first();
986
987 while (E) {
988 to_reload.push_back(E->self());
989 E = E->next();
990 }
991
992 ResourceCache::lock.unlock();
993
994 //now just make sure to not delete any of these resources while changing locale..
995 while (to_reload.front()) {
996 to_reload.front()->get()->reload_from_file();
997 to_reload.pop_front();
998 }
999}
1000
1001void ResourceLoader::load_translation_remaps() {
1002 if (!ProjectSettings::get_singleton()->has_setting("internationalization/locale/translation_remaps")) {
1003 return;
1004 }
1005
1006 Dictionary remaps = GLOBAL_GET("internationalization/locale/translation_remaps");
1007 List<Variant> keys;
1008 remaps.get_key_list(&keys);
1009 for (const Variant &E : keys) {
1010 Array langs = remaps[E];
1011 Vector<String> lang_remaps;
1012 lang_remaps.resize(langs.size());
1013 for (int i = 0; i < langs.size(); i++) {
1014 lang_remaps.write[i] = langs[i];
1015 }
1016
1017 translation_remaps[String(E)] = lang_remaps;
1018 }
1019}
1020
1021void ResourceLoader::clear_translation_remaps() {
1022 translation_remaps.clear();
1023 while (remapped_list.first() != nullptr) {
1024 remapped_list.remove(remapped_list.first());
1025 }
1026}
1027
1028void ResourceLoader::clear_thread_load_tasks() {
1029 // Bring the thing down as quickly as possible without causing deadlocks or leaks.
1030
1031 thread_load_mutex.lock();
1032 cleaning_tasks = true;
1033
1034 while (true) {
1035 bool none_running = true;
1036 if (thread_load_tasks.size()) {
1037 for (KeyValue<String, ResourceLoader::ThreadLoadTask> &E : thread_load_tasks) {
1038 if (E.value.status == THREAD_LOAD_IN_PROGRESS) {
1039 if (E.value.cond_var) {
1040 E.value.cond_var->notify_all();
1041 memdelete(E.value.cond_var);
1042 E.value.cond_var = nullptr;
1043 }
1044 none_running = false;
1045 }
1046 }
1047 }
1048 if (none_running) {
1049 break;
1050 }
1051 thread_load_mutex.unlock();
1052 OS::get_singleton()->delay_usec(1000);
1053 thread_load_mutex.lock();
1054 }
1055
1056 for (KeyValue<String, LoadToken *> &E : user_load_tokens) {
1057 memdelete(E.value);
1058 }
1059 user_load_tokens.clear();
1060
1061 thread_load_tasks.clear();
1062
1063 cleaning_tasks = false;
1064 thread_load_mutex.unlock();
1065}
1066
1067void ResourceLoader::load_path_remaps() {
1068 if (!ProjectSettings::get_singleton()->has_setting("path_remap/remapped_paths")) {
1069 return;
1070 }
1071
1072 Vector<String> remaps = GLOBAL_GET("path_remap/remapped_paths");
1073 int rc = remaps.size();
1074 ERR_FAIL_COND(rc & 1); //must be even
1075 const String *r = remaps.ptr();
1076
1077 for (int i = 0; i < rc; i += 2) {
1078 path_remaps[r[i]] = r[i + 1];
1079 }
1080}
1081
1082void ResourceLoader::clear_path_remaps() {
1083 path_remaps.clear();
1084}
1085
1086void ResourceLoader::set_load_callback(ResourceLoadedCallback p_callback) {
1087 _loaded_callback = p_callback;
1088}
1089
1090ResourceLoadedCallback ResourceLoader::_loaded_callback = nullptr;
1091
1092Ref<ResourceFormatLoader> ResourceLoader::_find_custom_resource_format_loader(String path) {
1093 for (int i = 0; i < loader_count; ++i) {
1094 if (loader[i]->get_script_instance() && loader[i]->get_script_instance()->get_script()->get_path() == path) {
1095 return loader[i];
1096 }
1097 }
1098 return Ref<ResourceFormatLoader>();
1099}
1100
1101bool ResourceLoader::add_custom_resource_format_loader(String script_path) {
1102 if (_find_custom_resource_format_loader(script_path).is_valid()) {
1103 return false;
1104 }
1105
1106 Ref<Resource> res = ResourceLoader::load(script_path);
1107 ERR_FAIL_COND_V(res.is_null(), false);
1108 ERR_FAIL_COND_V(!res->is_class("Script"), false);
1109
1110 Ref<Script> s = res;
1111 StringName ibt = s->get_instance_base_type();
1112 bool valid_type = ClassDB::is_parent_class(ibt, "ResourceFormatLoader");
1113 ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceLoader: " + script_path + ".");
1114
1115 Object *obj = ClassDB::instantiate(ibt);
1116
1117 ERR_FAIL_NULL_V_MSG(obj, false, "Cannot instance script as custom resource loader, expected 'ResourceFormatLoader' inheritance, got: " + String(ibt) + ".");
1118
1119 Ref<ResourceFormatLoader> crl = Object::cast_to<ResourceFormatLoader>(obj);
1120 crl->set_script(s);
1121 ResourceLoader::add_resource_format_loader(crl);
1122
1123 return true;
1124}
1125
1126void ResourceLoader::set_create_missing_resources_if_class_unavailable(bool p_enable) {
1127 create_missing_resources_if_class_unavailable = p_enable;
1128}
1129
1130void ResourceLoader::add_custom_loaders() {
1131 // Custom loaders registration exploits global class names
1132
1133 String custom_loader_base_class = ResourceFormatLoader::get_class_static();
1134
1135 List<StringName> global_classes;
1136 ScriptServer::get_global_class_list(&global_classes);
1137
1138 for (const StringName &class_name : global_classes) {
1139 StringName base_class = ScriptServer::get_global_class_native_base(class_name);
1140
1141 if (base_class == custom_loader_base_class) {
1142 String path = ScriptServer::get_global_class_path(class_name);
1143 add_custom_resource_format_loader(path);
1144 }
1145 }
1146}
1147
1148void ResourceLoader::remove_custom_loaders() {
1149 Vector<Ref<ResourceFormatLoader>> custom_loaders;
1150 for (int i = 0; i < loader_count; ++i) {
1151 if (loader[i]->get_script_instance()) {
1152 custom_loaders.push_back(loader[i]);
1153 }
1154 }
1155
1156 for (int i = 0; i < custom_loaders.size(); ++i) {
1157 remove_resource_format_loader(custom_loaders[i]);
1158 }
1159}
1160
1161bool ResourceLoader::is_cleaning_tasks() {
1162 MutexLock lock(thread_load_mutex);
1163 return cleaning_tasks;
1164}
1165
1166void ResourceLoader::initialize() {}
1167
1168void ResourceLoader::finalize() {}
1169
1170ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr;
1171DependencyErrorNotify ResourceLoader::dep_err_notify = nullptr;
1172
1173bool ResourceLoader::create_missing_resources_if_class_unavailable = false;
1174bool ResourceLoader::abort_on_missing_resource = true;
1175bool ResourceLoader::timestamp_on_load = false;
1176
1177thread_local int ResourceLoader::load_nesting = 0;
1178thread_local WorkerThreadPool::TaskID ResourceLoader::caller_task_id = 0;
1179thread_local Vector<String> *ResourceLoader::load_paths_stack;
1180
1181template <>
1182thread_local uint32_t SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG>::count = 0;
1183SafeBinaryMutex<ResourceLoader::BINARY_MUTEX_TAG> ResourceLoader::thread_load_mutex;
1184HashMap<String, ResourceLoader::ThreadLoadTask> ResourceLoader::thread_load_tasks;
1185bool ResourceLoader::cleaning_tasks = false;
1186
1187HashMap<String, ResourceLoader::LoadToken *> ResourceLoader::user_load_tokens;
1188
1189SelfList<Resource>::List ResourceLoader::remapped_list;
1190HashMap<String, Vector<String>> ResourceLoader::translation_remaps;
1191HashMap<String, String> ResourceLoader::path_remaps;
1192
1193ResourceLoaderImport ResourceLoader::import = nullptr;
1194