2014-02-10 02:10:30 +01:00
/**************************************************************************/
/* resource_loader.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-10 02:10:30 +01:00
# include "resource_loader.h"
2018-09-11 18:13:45 +02:00
2020-11-07 23:33:38 +01:00
# include "core/config/project_settings.h"
2021-06-11 14:51:48 +02:00
# include "core/io/file_access.h"
2019-02-12 13:30:56 +01:00
# include "core/io/resource_importer.h"
2023-09-06 21:02:52 +02:00
# include "core/object/script_language.h"
2023-02-20 19:00:26 +01:00
# include "core/os/condition_variable.h"
2018-09-11 18:13:45 +02:00
# include "core/os/os.h"
2024-02-04 16:59:54 +01:00
# include "core/os/safe_binary_mutex.h"
2020-11-07 23:33:38 +01:00
# include "core/string/print_string.h"
# include "core/string/translation.h"
# include "core/variant/variant_parser.h"
2024-06-28 11:16:59 +02:00
# include "servers/rendering_server.h"
2018-09-11 18:13:45 +02:00
2020-02-28 12:27:04 +01:00
# ifdef DEBUG_LOAD_THREADED
# define print_lt(m_text) print_line(m_text)
# else
# define print_lt(m_text)
# endif
2018-06-11 02:59:53 +02:00
Ref < ResourceFormatLoader > ResourceLoader : : loader [ ResourceLoader : : MAX_LOADERS ] ;
2014-02-10 02:10:30 +01:00
int ResourceLoader : : loader_count = 0 ;
2017-01-26 01:55:59 +01:00
bool ResourceFormatLoader : : recognize_path ( const String & p_path , const String & p_for_type ) const {
2022-10-11 11:40:04 +02:00
bool ret = false ;
if ( GDVIRTUAL_CALL ( _recognize_path , p_path , p_for_type , ret ) ) {
return ret ;
}
2017-01-26 01:55:59 +01:00
String extension = p_path . get_extension ( ) ;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
List < String > extensions ;
2021-12-09 10:42:46 +01:00
if ( p_for_type . is_empty ( ) ) {
2017-01-26 01:55:59 +01:00
get_recognized_extensions ( & extensions ) ;
} else {
get_recognized_extensions_for_type ( p_for_type , & extensions ) ;
}
2021-07-24 15:46:25 +02:00
for ( const String & E : extensions ) {
2021-07-16 05:45:57 +02:00
if ( E . nocasecmp_to ( extension ) = = 0 ) {
2014-02-10 02:10:30 +01:00
return true ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
return false ;
}
2018-06-11 02:59:53 +02:00
bool ResourceFormatLoader : : handles_type ( const String & p_type ) const {
2022-09-29 11:53:28 +02:00
bool success = false ;
2022-10-18 18:47:44 +02:00
GDVIRTUAL_CALL ( _handles_type , p_type , success ) ;
return success ;
2018-06-11 02:59:53 +02:00
}
2022-07-14 14:18:18 +02:00
void ResourceFormatLoader : : get_classes_used ( const String & p_path , HashSet < StringName > * r_classes ) {
Vector < String > ret ;
if ( GDVIRTUAL_CALL ( _get_classes_used , p_path , ret ) ) {
for ( int i = 0 ; i < ret . size ( ) ; i + + ) {
r_classes - > insert ( ret [ i ] ) ;
}
return ;
}
String res = get_resource_type ( p_path ) ;
if ( ! res . is_empty ( ) ) {
r_classes - > insert ( res ) ;
}
}
2018-06-11 02:59:53 +02:00
String ResourceFormatLoader : : get_resource_type ( const String & p_path ) const {
2021-08-22 03:52:44 +02:00
String ret ;
2022-10-18 18:47:44 +02:00
GDVIRTUAL_CALL ( _get_resource_type , p_path , ret ) ;
return ret ;
2018-06-11 02:59:53 +02:00
}
2023-01-19 19:12:25 +01:00
String ResourceFormatLoader : : get_resource_script_class ( const String & p_path ) const {
String ret ;
GDVIRTUAL_CALL ( _get_resource_script_class , p_path , ret ) ;
return ret ;
}
2021-07-23 21:01:18 +02:00
ResourceUID : : ID ResourceFormatLoader : : get_resource_uid ( const String & p_path ) const {
2022-09-29 11:53:28 +02:00
int64_t uid = ResourceUID : : INVALID_ID ;
2022-10-18 18:47:44 +02:00
GDVIRTUAL_CALL ( _get_resource_uid , p_path , uid ) ;
return uid ;
2021-07-23 21:01:18 +02:00
}
2014-02-10 02:10:30 +01:00
void ResourceFormatLoader : : get_recognized_extensions_for_type ( const String & p_type , List < String > * p_extensions ) const {
2021-12-09 10:42:46 +01:00
if ( p_type . is_empty ( ) | | handles_type ( p_type ) ) {
2014-02-10 02:10:30 +01:00
get_recognized_extensions ( p_extensions ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
void ResourceLoader : : get_recognized_extensions_for_type ( const String & p_type , List < String > * p_extensions ) {
for ( int i = 0 ; i < loader_count ; i + + ) {
loader [ i ] - > get_recognized_extensions_for_type ( p_type , p_extensions ) ;
}
}
2018-08-10 20:57:43 +02:00
bool ResourceFormatLoader : : exists ( const String & p_path ) const {
2022-09-29 11:53:28 +02:00
bool success = false ;
2021-08-22 03:52:44 +02:00
if ( GDVIRTUAL_CALL ( _exists , p_path , success ) ) {
return success ;
}
2022-10-18 18:47:44 +02:00
return FileAccess : : exists ( p_path ) ; // By default just check file.
2018-08-10 20:57:43 +02:00
}
2018-08-12 12:44:38 +02:00
2018-06-11 02:59:53 +02:00
void ResourceFormatLoader : : get_recognized_extensions ( List < String > * p_extensions ) const {
2021-08-22 03:52:44 +02:00
PackedStringArray exts ;
if ( GDVIRTUAL_CALL ( _get_recognized_extensions , exts ) ) {
const String * r = exts . ptr ( ) ;
for ( int i = 0 ; i < exts . size ( ) ; + + i ) {
p_extensions - > push_back ( r [ i ] ) ;
2018-06-11 02:59:53 +02:00
}
}
}
2022-05-03 01:43:50 +02:00
Ref < 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 ) {
2021-08-22 03:52:44 +02:00
Variant res ;
if ( GDVIRTUAL_CALL ( _load , p_path , p_original_path , p_use_sub_threads , p_cache_mode , res ) ) {
2021-05-05 10:56:26 +02:00
if ( res . get_type ( ) = = Variant : : INT ) { // Error code, abort.
2020-05-14 16:41:43 +02:00
if ( r_error ) {
2018-06-11 02:59:53 +02:00
* r_error = ( Error ) res . operator int64_t ( ) ;
2020-05-14 16:41:43 +02:00
}
2022-05-03 01:43:50 +02:00
return Ref < Resource > ( ) ;
2021-05-05 10:56:26 +02:00
} else { // Success, pass on result.
2020-05-14 16:41:43 +02:00
if ( r_error ) {
2018-06-11 02:59:53 +02:00
* r_error = OK ;
2020-05-14 16:41:43 +02:00
}
2018-06-11 02:59:53 +02:00
return res ;
}
2014-02-10 02:10:30 +01:00
}
2020-02-28 12:27:04 +01:00
2022-05-03 01:43:50 +02:00
ERR_FAIL_V_MSG ( Ref < Resource > ( ) , " Failed to load resource ' " + p_path + " '. ResourceFormatLoader::load was not implemented for this resource type. " ) ;
2014-02-10 02:10:30 +01:00
}
2015-08-24 01:15:56 +02:00
void ResourceFormatLoader : : get_dependencies ( const String & p_path , List < String > * p_dependencies , bool p_add_types ) {
2021-08-22 03:52:44 +02:00
PackedStringArray deps ;
if ( GDVIRTUAL_CALL ( _get_dependencies , p_path , p_add_types , deps ) ) {
const String * r = deps . ptr ( ) ;
for ( int i = 0 ; i < deps . size ( ) ; + + i ) {
p_dependencies - > push_back ( r [ i ] ) ;
2018-06-11 02:59:53 +02:00
}
}
}
2022-05-13 15:04:37 +02:00
Error ResourceFormatLoader : : rename_dependencies ( const String & p_path , const HashMap < String , String > & p_map ) {
2021-08-22 03:52:44 +02:00
Dictionary deps_dict ;
2021-08-09 22:13:42 +02:00
for ( KeyValue < String , String > E : p_map ) {
deps_dict [ E . key ] = E . value ;
2021-08-22 03:52:44 +02:00
}
2018-06-11 02:59:53 +02:00
2023-01-15 20:33:20 +01:00
Error err = OK ;
2022-10-18 18:47:44 +02:00
GDVIRTUAL_CALL ( _rename_dependencies , p_path , deps_dict , err ) ;
2023-01-15 20:33:20 +01:00
return err ;
2018-06-11 02:59:53 +02:00
}
void ResourceFormatLoader : : _bind_methods ( ) {
2021-02-19 13:35:31 +01:00
BIND_ENUM_CONSTANT ( CACHE_MODE_IGNORE ) ;
BIND_ENUM_CONSTANT ( CACHE_MODE_REUSE ) ;
BIND_ENUM_CONSTANT ( CACHE_MODE_REPLACE ) ;
2024-02-22 12:53:19 +01:00
BIND_ENUM_CONSTANT ( CACHE_MODE_IGNORE_DEEP ) ;
BIND_ENUM_CONSTANT ( CACHE_MODE_REPLACE_DEEP ) ;
2021-08-22 03:52:44 +02:00
GDVIRTUAL_BIND ( _get_recognized_extensions ) ;
2022-10-11 11:40:04 +02:00
GDVIRTUAL_BIND ( _recognize_path , " path " , " type " ) ;
2021-08-22 03:52:44 +02:00
GDVIRTUAL_BIND ( _handles_type , " type " ) ;
GDVIRTUAL_BIND ( _get_resource_type , " path " ) ;
2023-01-19 19:12:25 +01:00
GDVIRTUAL_BIND ( _get_resource_script_class , " path " ) ;
2021-08-22 03:52:44 +02:00
GDVIRTUAL_BIND ( _get_resource_uid , " path " ) ;
GDVIRTUAL_BIND ( _get_dependencies , " path " , " add_types " ) ;
GDVIRTUAL_BIND ( _rename_dependencies , " path " , " renames " ) ;
GDVIRTUAL_BIND ( _exists , " path " ) ;
2022-07-14 14:18:18 +02:00
GDVIRTUAL_BIND ( _get_classes_used , " path " ) ;
2021-08-22 03:52:44 +02:00
GDVIRTUAL_BIND ( _load , " path " , " original_path " , " use_sub_threads " , " cache_mode " ) ;
2014-02-10 02:10:30 +01:00
}
///////////////////////////////////
2023-03-05 01:09:18 +01:00
// This should be robust enough to be called redundantly without issues.
void ResourceLoader : : LoadToken : : clear ( ) {
thread_load_mutex . lock ( ) ;
2023-05-10 11:23:07 +02:00
WorkerThreadPool : : TaskID task_to_await = 0 ;
2023-03-05 01:09:18 +01:00
if ( ! local_path . is_empty ( ) ) { // Empty is used for the special case where the load task is not registered.
DEV_ASSERT ( thread_load_tasks . has ( local_path ) ) ;
ThreadLoadTask & load_task = thread_load_tasks [ local_path ] ;
2023-05-10 11:23:07 +02:00
if ( ! load_task . awaited ) {
task_to_await = load_task . task_id ;
load_task . awaited = true ;
}
2023-03-05 01:09:18 +01:00
thread_load_tasks . erase ( local_path ) ;
local_path . clear ( ) ;
}
if ( ! user_path . is_empty ( ) ) {
DEV_ASSERT ( user_load_tokens . has ( user_path ) ) ;
user_load_tokens . erase ( user_path ) ;
user_path . clear ( ) ;
}
thread_load_mutex . unlock ( ) ;
2023-05-10 11:23:07 +02:00
// If task is unused, await it here, locally, now the token data is consistent.
if ( task_to_await ) {
WorkerThreadPool : : get_singleton ( ) - > wait_for_task_completion ( task_to_await ) ;
2023-03-05 01:09:18 +01:00
}
}
ResourceLoader : : LoadToken : : ~ LoadToken ( ) {
clear ( ) ;
}
2022-05-03 01:43:50 +02:00
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 ) {
2024-04-01 07:56:47 +02:00
const String & original_path = p_original_path . is_empty ( ) ? p_path : p_original_path ;
2023-03-05 01:09:18 +01:00
load_nesting + + ;
2023-07-03 13:54:55 +02:00
if ( load_paths_stack - > size ( ) ) {
2023-03-05 01:09:18 +01:00
thread_load_mutex . lock ( ) ;
2024-04-01 07:56:47 +02:00
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 ) {
2024-01-29 18:27:57 +01:00
E - > value . sub_tasks . insert ( p_original_path ) ;
2023-03-05 01:09:18 +01:00
}
thread_load_mutex . unlock ( ) ;
}
2024-04-01 07:56:47 +02:00
load_paths_stack - > push_back ( original_path ) ;
2017-08-31 23:57:03 +02:00
// Try all loaders and pick the first match for the type hint
2023-03-05 01:09:18 +01:00
bool found = false ;
Ref < Resource > res ;
2017-08-31 23:57:03 +02:00
for ( int i = 0 ; i < loader_count ; i + + ) {
if ( ! loader [ i ] - > recognize_path ( p_path , p_type_hint ) ) {
continue ;
}
found = true ;
2024-04-01 07:56:47 +02:00
res = loader [ i ] - > load ( p_path , original_path , r_error , p_use_sub_threads , r_progress , p_cache_mode ) ;
2023-03-05 01:09:18 +01:00
if ( ! res . is_null ( ) ) {
break ;
2017-08-31 23:57:03 +02:00
}
2023-03-05 01:09:18 +01:00
}
2017-08-31 23:57:03 +02:00
2023-07-03 13:54:55 +02:00
load_paths_stack - > resize ( load_paths_stack - > size ( ) - 1 ) ;
2024-06-25 14:12:40 +02:00
res_ref_overrides . erase ( load_nesting ) ;
2023-03-05 01:09:18 +01:00
load_nesting - - ;
if ( ! res . is_null ( ) ) {
2017-08-31 23:57:03 +02:00
return res ;
}
2022-05-03 01:43:50 +02:00
ERR_FAIL_COND_V_MSG ( found , Ref < Resource > ( ) ,
2020-08-22 22:19:08 +02:00
vformat ( " Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once. " , p_path ) ) ;
2019-08-15 04:57:49 +02:00
2020-01-08 15:11:16 +01:00
# ifdef TOOLS_ENABLED
2022-03-23 10:08:58 +01:00
Ref < FileAccess > file_check = FileAccess : : create ( FileAccess : : ACCESS_RESOURCES ) ;
2023-07-21 04:45:34 +02:00
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 ) ) ;
2020-01-08 15:11:16 +01:00
# endif
2023-07-21 04:45:34 +02:00
ERR_FAIL_V_MSG ( Ref < Resource > ( ) , vformat ( " No loader found for resource: %s (expected type: %s) " , p_path , p_type_hint ) ) ;
2017-08-31 23:57:03 +02:00
}
2020-02-28 12:27:04 +01:00
void ResourceLoader : : _thread_load_function ( void * p_userdata ) {
ThreadLoadTask & load_task = * ( ThreadLoadTask * ) p_userdata ;
2023-05-10 11:23:07 +02:00
thread_load_mutex . lock ( ) ;
caller_task_id = load_task . task_id ;
if ( cleaning_tasks ) {
load_task . status = THREAD_LOAD_FAILED ;
thread_load_mutex . unlock ( ) ;
return ;
}
thread_load_mutex . unlock ( ) ;
2023-03-05 01:09:18 +01:00
// Thread-safe either if it's the current thread or a brand new one.
2024-06-13 10:26:33 +02:00
CallQueue * own_mq_override = nullptr ;
2023-05-10 11:23:07 +02:00
if ( load_nesting = = 0 ) {
2023-07-03 13:54:55 +02:00
load_paths_stack = memnew ( Vector < String > ) ;
2023-05-10 10:00:33 +02:00
if ( ! Thread : : is_main_thread ( ) ) {
2024-06-13 10:26:33 +02:00
// Let the caller thread use its own, for added flexibility. Provide one otherwise.
if ( MessageQueue : : get_singleton ( ) = = MessageQueue : : get_main_singleton ( ) ) {
own_mq_override = memnew ( CallQueue ) ;
MessageQueue : : set_thread_singleton_override ( own_mq_override ) ;
}
2023-05-23 23:28:47 +02:00
set_current_thread_safe_for_nodes ( true ) ;
2023-05-10 10:00:33 +02:00
}
2020-02-28 12:27:04 +01:00
}
2023-03-05 01:09:18 +01:00
// --
2020-02-28 12:27:04 +01:00
2024-07-15 11:43:27 +02:00
Error load_err = OK ;
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_err , load_task . use_sub_threads , & load_task . progress ) ;
2024-07-09 18:41:24 +02:00
if ( MessageQueue : : get_singleton ( ) ! = MessageQueue : : get_main_singleton ( ) ) {
2024-06-13 10:26:33 +02:00
MessageQueue : : get_singleton ( ) - > flush ( ) ;
2023-05-19 10:46:49 +02:00
}
2023-03-05 01:09:18 +01:00
thread_load_mutex . lock ( ) ;
load_task . resource = res ;
2019-02-16 21:38:09 +01:00
2024-07-15 11:43:27 +02:00
load_task . progress = 1.0 ; // It was fully loaded at this point, so force progress to 1.0.
load_task . error = load_err ;
2020-02-28 12:27:04 +01:00
if ( load_task . error ! = OK ) {
load_task . status = THREAD_LOAD_FAILED ;
2019-01-27 23:24:55 +01:00
} else {
2020-02-28 12:27:04 +01:00
load_task . status = THREAD_LOAD_LOADED ;
2019-01-27 23:24:55 +01:00
}
2023-03-05 01:09:18 +01:00
if ( load_task . cond_var ) {
2023-02-20 19:00:26 +01:00
load_task . cond_var - > notify_all ( ) ;
memdelete ( load_task . cond_var ) ;
load_task . cond_var = nullptr ;
2020-02-28 12:27:04 +01:00
}
2019-02-16 21:38:09 +01:00
2024-02-22 12:53:19 +01:00
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 ;
2020-02-28 12:27:04 +01:00
if ( load_task . resource . is_valid ( ) ) {
2024-02-22 12:53:19 +01:00
if ( ! ignoring ) {
if ( replacing ) {
2024-01-09 15:48:48 +01:00
Ref < Resource > old_res = ResourceCache : : get_ref ( load_task . local_path ) ;
if ( old_res . is_valid ( ) & & old_res ! = load_task . resource ) {
// If resource is already loaded, only replace its data, to avoid existing invalidating instances.
old_res - > copy_from ( load_task . resource ) ;
load_task . resource = old_res ;
}
}
2024-02-22 12:53:19 +01:00
load_task . resource - > set_path ( load_task . local_path , replacing ) ;
} else {
2023-08-05 02:07:16 +02:00
load_task . resource - > set_path_cache ( load_task . local_path ) ;
2023-03-05 01:09:18 +01:00
}
2019-02-16 21:38:09 +01:00
2020-05-14 16:41:43 +02:00
if ( load_task . xl_remapped ) {
2020-02-28 12:27:04 +01:00
load_task . resource - > set_as_translation_remapped ( true ) ;
2020-05-14 16:41:43 +02:00
}
2019-02-16 21:38:09 +01:00
2020-02-28 12:27:04 +01:00
# ifdef TOOLS_ENABLED
load_task . resource - > set_edited ( false ) ;
if ( timestamp_on_load ) {
uint64_t mt = FileAccess : : get_modified_time ( load_task . remapped_path ) ;
//printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt);
load_task . resource - > set_last_modified_time ( mt ) ;
}
# endif
2019-01-27 23:24:55 +01:00
2020-02-28 12:27:04 +01:00
if ( _loaded_callback ) {
_loaded_callback ( load_task . resource , load_task . local_path ) ;
}
2024-02-22 12:53:19 +01:00
} else if ( ! ignoring ) {
2024-01-09 15:48:48 +01:00
Ref < Resource > existing = ResourceCache : : get_ref ( load_task . local_path ) ;
if ( existing . is_valid ( ) ) {
load_task . resource = existing ;
load_task . status = THREAD_LOAD_LOADED ;
load_task . progress = 1.0 ;
if ( _loaded_callback ) {
_loaded_callback ( load_task . resource , load_task . local_path ) ;
}
}
2020-02-28 12:27:04 +01:00
}
2015-08-24 01:15:56 +02:00
2023-03-05 01:09:18 +01:00
thread_load_mutex . unlock ( ) ;
2023-05-10 10:00:33 +02:00
2023-07-03 13:54:55 +02:00
if ( load_nesting = = 0 ) {
2024-06-13 10:26:33 +02:00
if ( own_mq_override ) {
2024-06-13 10:14:14 +02:00
MessageQueue : : set_thread_singleton_override ( nullptr ) ;
2024-06-13 10:26:33 +02:00
memdelete ( own_mq_override ) ;
2023-07-03 13:54:55 +02:00
}
memdelete ( load_paths_stack ) ;
2023-05-10 10:00:33 +02:00
}
2020-02-28 12:27:04 +01:00
}
2020-05-14 14:29:06 +02:00
2021-07-23 21:01:18 +02:00
static String _validate_local_path ( const String & p_path ) {
ResourceUID : : ID uid = ResourceUID : : get_singleton ( ) - > text_to_id ( p_path ) ;
if ( uid ! = ResourceUID : : INVALID_ID ) {
return ResourceUID : : get_singleton ( ) - > get_id_path ( uid ) ;
2021-08-30 01:43:47 +02:00
} else if ( p_path . is_relative_path ( ) ) {
2024-03-30 12:49:52 +01:00
return ( " res:// " + p_path ) . simplify_path ( ) ;
2020-05-14 16:41:43 +02:00
} else {
2021-07-23 21:01:18 +02:00
return ProjectSettings : : get_singleton ( ) - > localize_path ( p_path ) ;
2020-05-14 16:41:43 +02:00
}
2021-07-23 21:01:18 +02:00
}
2014-02-10 02:10:30 +01:00
2023-03-05 01:09:18 +01:00
Error ResourceLoader : : load_threaded_request ( const String & p_path , const String & p_type_hint , bool p_use_sub_threads , ResourceFormatLoader : : CacheMode p_cache_mode ) {
thread_load_mutex . lock ( ) ;
if ( user_load_tokens . has ( p_path ) ) {
print_verbose ( " load_threaded_request(): Another threaded load for resource path ' " + p_path + " ' has been initiated. Not an error. " ) ;
user_load_tokens [ p_path ] - > reference ( ) ; // Additional request.
thread_load_mutex . unlock ( ) ;
return OK ;
}
user_load_tokens [ p_path ] = nullptr ;
thread_load_mutex . unlock ( ) ;
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 ) ;
if ( token . is_valid ( ) ) {
thread_load_mutex . lock ( ) ;
token - > user_path = p_path ;
token - > reference ( ) ; // First request.
user_load_tokens [ p_path ] = token . ptr ( ) ;
print_lt ( " REQUEST: user load tokens: " + itos ( user_load_tokens . size ( ) ) ) ;
thread_load_mutex . unlock ( ) ;
return OK ;
} else {
return FAILED ;
}
}
2019-01-27 23:24:55 +01:00
2023-03-05 01:09:18 +01:00
Ref < Resource > ResourceLoader : : load ( const String & p_path , const String & p_type_hint , ResourceFormatLoader : : CacheMode p_cache_mode , Error * r_error ) {
if ( r_error ) {
* r_error = OK ;
2020-02-28 12:27:04 +01:00
}
2014-02-10 02:10:30 +01:00
2023-03-05 01:09:18 +01:00
Ref < LoadToken > load_token = _load_start ( p_path , p_type_hint , LOAD_THREAD_FROM_CURRENT , p_cache_mode ) ;
if ( ! load_token . is_valid ( ) ) {
if ( r_error ) {
* r_error = FAILED ;
2020-02-28 12:27:04 +01:00
}
2023-03-05 01:09:18 +01:00
return Ref < Resource > ( ) ;
2020-02-28 12:27:04 +01:00
}
2018-09-02 18:59:33 +02:00
2023-03-05 01:09:18 +01:00
Ref < Resource > res = _load_complete ( * load_token . ptr ( ) , r_error ) ;
return res ;
}
2020-02-28 12:27:04 +01:00
2023-03-05 01:09:18 +01:00
Ref < ResourceLoader : : LoadToken > ResourceLoader : : _load_start ( const String & p_path , const String & p_type_hint , LoadThreadMode p_thread_mode , ResourceFormatLoader : : CacheMode p_cache_mode ) {
String local_path = _validate_local_path ( p_path ) ;
2020-02-28 12:27:04 +01:00
2024-06-24 11:25:57 +02:00
bool ignoring_cache = p_cache_mode = = ResourceFormatLoader : : CACHE_MODE_IGNORE | | p_cache_mode = = ResourceFormatLoader : : CACHE_MODE_IGNORE_DEEP ;
2023-03-05 01:09:18 +01:00
Ref < LoadToken > load_token ;
ThreadLoadTask unregistered_load_task ; // Once set, must be valid up to the call to do the load.
ThreadLoadTask * load_task_ptr = nullptr ;
bool run_on_current_thread = false ;
{
MutexLock thread_load_lock ( thread_load_mutex ) ;
2020-02-28 12:27:04 +01:00
2024-06-24 11:25:57 +02:00
if ( ! ignoring_cache & & thread_load_tasks . has ( local_path ) ) {
2023-03-05 01:09:18 +01:00
load_token = Ref < LoadToken > ( thread_load_tasks [ local_path ] . load_token ) ;
2024-07-09 18:41:24 +02:00
if ( load_token . is_valid ( ) ) {
return load_token ;
} else {
2023-03-05 01:09:18 +01:00
// The token is dying (reached 0 on another thread).
// Ensure it's killed now so the path can be safely reused right away.
thread_load_tasks [ local_path ] . load_token - > clear ( ) ;
2020-02-28 12:27:04 +01:00
}
2023-03-05 01:09:18 +01:00
}
2022-06-22 13:46:46 +02:00
2023-03-05 01:09:18 +01:00
load_token . instantiate ( ) ;
load_token - > local_path = local_path ;
2022-06-22 13:46:46 +02:00
2023-03-05 01:09:18 +01:00
//create load task
{
ThreadLoadTask load_task ;
load_task . remapped_path = _path_remap ( local_path , & load_task . xl_remapped ) ;
load_task . load_token = load_token . ptr ( ) ;
load_task . local_path = local_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 ;
2024-01-09 15:48:48 +01:00
if ( p_cache_mode = = ResourceFormatLoader : : CACHE_MODE_REUSE ) {
2023-03-05 01:09:18 +01:00
Ref < Resource > existing = ResourceCache : : get_ref ( local_path ) ;
if ( existing . is_valid ( ) ) {
//referencing is fine
load_task . resource = existing ;
load_task . status = THREAD_LOAD_LOADED ;
load_task . progress = 1.0 ;
thread_load_tasks [ local_path ] = load_task ;
return load_token ;
}
2020-02-28 12:27:04 +01:00
}
2024-06-24 11:25:57 +02:00
// Cache-ignoring tasks aren't registered in the map and so must finish within scope.
if ( ignoring_cache ) {
2023-03-05 01:09:18 +01:00
load_token - > local_path . clear ( ) ;
unregistered_load_task = load_task ;
2024-06-24 11:25:57 +02:00
load_task_ptr = & unregistered_load_task ;
2023-03-05 01:09:18 +01:00
} else {
2024-06-24 11:25:57 +02:00
DEV_ASSERT ( ! thread_load_tasks . has ( local_path ) ) ;
HashMap < String , ResourceLoader : : ThreadLoadTask > : : Iterator E = thread_load_tasks . insert ( local_path , load_task ) ;
load_task_ptr = & E - > value ;
2023-03-05 01:09:18 +01:00
}
2018-09-02 18:59:33 +02:00
}
2020-02-28 12:27:04 +01:00
2024-06-24 11:25:57 +02:00
run_on_current_thread = ignoring_cache | | p_thread_mode = = LOAD_THREAD_FROM_CURRENT ;
2017-12-14 19:33:54 +01:00
2023-03-05 01:09:18 +01:00
if ( run_on_current_thread ) {
2023-05-10 11:23:07 +02:00
load_task_ptr - > thread_id = Thread : : get_caller_id ( ) ;
2023-03-05 01:09:18 +01:00
} else {
2023-05-10 11:23:07 +02:00
load_task_ptr - > task_id = WorkerThreadPool : : get_singleton ( ) - > add_native_task ( & ResourceLoader : : _thread_load_function , load_task_ptr ) ;
2023-03-05 01:09:18 +01:00
}
2019-01-27 23:24:55 +01:00
}
2017-12-14 19:33:54 +01:00
2023-03-05 01:09:18 +01:00
if ( run_on_current_thread ) {
_thread_load_function ( load_task_ptr ) ;
2024-06-24 11:25:57 +02:00
if ( ignoring_cache ) {
2023-05-17 00:00:45 +02:00
load_token - > res_if_unregistered = load_task_ptr - > resource ;
}
2023-03-05 01:09:18 +01:00
}
2020-02-28 12:27:04 +01:00
2023-03-05 01:09:18 +01:00
return load_token ;
2020-02-28 12:27:04 +01:00
}
float ResourceLoader : : _dependency_get_progress ( const String & p_path ) {
if ( thread_load_tasks . has ( p_path ) ) {
ThreadLoadTask & load_task = thread_load_tasks [ p_path ] ;
2024-01-05 19:17:51 +01:00
float current_progress = 0.0 ;
2020-02-28 12:27:04 +01:00
int dep_count = load_task . sub_tasks . size ( ) ;
if ( dep_count > 0 ) {
2022-05-19 01:43:40 +02:00
for ( const String & E : load_task . sub_tasks ) {
2024-01-05 19:17:51 +01:00
current_progress + = _dependency_get_progress ( E ) ;
2020-02-28 12:27:04 +01:00
}
2024-01-05 19:17:51 +01:00
current_progress / = float ( dep_count ) ;
current_progress * = 0.5 ;
current_progress + = load_task . progress * 0.5 ;
2020-02-28 12:27:04 +01:00
} else {
2024-01-05 19:17:51 +01:00
current_progress = load_task . progress ;
2019-01-27 23:24:55 +01:00
}
2024-01-05 19:17:51 +01:00
load_task . max_reported_progress = MAX ( load_task . max_reported_progress , current_progress ) ;
return load_task . max_reported_progress ;
2020-02-28 12:27:04 +01:00
} else {
return 1.0 ; //assume finished loading it so it no longer exists
2017-08-31 23:57:03 +02:00
}
2020-02-28 12:27:04 +01:00
}
2017-06-28 22:00:18 +02:00
2020-02-28 12:27:04 +01:00
ResourceLoader : : ThreadLoadStatus ResourceLoader : : load_threaded_get_status ( const String & p_path , float * r_progress ) {
2024-07-09 18:41:24 +02:00
bool ensure_progress = false ;
ThreadLoadStatus status = THREAD_LOAD_IN_PROGRESS ;
{
MutexLock thread_load_lock ( thread_load_mutex ) ;
2014-02-10 02:10:30 +01:00
2024-07-09 18:41:24 +02:00
if ( ! user_load_tokens . has ( p_path ) ) {
print_verbose ( " load_threaded_get_status(): No threaded load for resource path ' " + p_path + " ' has been initiated or its result has already been collected. " ) ;
return THREAD_LOAD_INVALID_RESOURCE ;
}
2023-03-05 01:09:18 +01:00
2024-07-09 18:41:24 +02:00
String local_path = _validate_local_path ( p_path ) ;
if ( ! thread_load_tasks . has ( local_path ) ) {
2023-03-05 01:09:18 +01:00
# ifdef DEV_ENABLED
2024-07-09 18:41:24 +02:00
CRASH_NOW ( ) ;
2023-03-05 01:09:18 +01:00
# endif
2024-07-09 18:41:24 +02:00
// On non-dev, be defensive and at least avoid crashing (at this point at least).
return THREAD_LOAD_INVALID_RESOURCE ;
}
2023-03-05 01:09:18 +01:00
2024-07-09 18:41:24 +02:00
ThreadLoadTask & load_task = thread_load_tasks [ local_path ] ;
status = load_task . status ;
if ( r_progress ) {
* r_progress = _dependency_get_progress ( local_path ) ;
}
2019-01-27 23:24:55 +01:00
2024-07-09 18:41:24 +02:00
// 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_progress = true ;
} else {
load_task . last_progress_check_main_thread_frame = frame ;
}
2024-06-28 11:16:59 +02:00
}
}
2024-07-09 18:41:24 +02:00
if ( ensure_progress ) {
_ensure_load_progress ( ) ;
}
2020-02-28 12:27:04 +01:00
return status ;
2014-02-10 02:10:30 +01:00
}
2020-05-14 14:29:06 +02:00
2022-05-03 01:43:50 +02:00
Ref < Resource > ResourceLoader : : load_threaded_get ( const String & p_path , Error * r_error ) {
2023-03-05 01:09:18 +01:00
if ( r_error ) {
* r_error = OK ;
2018-08-10 20:57:43 +02:00
}
2023-03-05 01:09:18 +01:00
Ref < Resource > res ;
{
MutexLock thread_load_lock ( thread_load_mutex ) ;
2018-08-10 20:57:43 +02:00
2023-03-05 01:09:18 +01:00
if ( ! user_load_tokens . has ( p_path ) ) {
print_verbose ( " load_threaded_get(): No threaded load for resource path ' " + p_path + " ' has been initiated or its result has already been collected. " ) ;
2023-02-26 17:17:41 +01:00
if ( r_error ) {
2023-03-05 01:09:18 +01:00
* r_error = ERR_INVALID_PARAMETER ;
2023-02-26 17:17:41 +01:00
}
return Ref < Resource > ( ) ;
2018-08-10 20:57:43 +02:00
}
2023-03-05 01:09:18 +01:00
LoadToken * load_token = user_load_tokens [ p_path ] ;
if ( ! load_token ) {
// This happens if requested from one thread and rapidly querying from another.
2020-02-28 12:27:04 +01:00
if ( r_error ) {
2023-03-05 01:09:18 +01:00
* r_error = ERR_BUSY ;
2020-02-28 12:27:04 +01:00
}
2022-05-03 01:43:50 +02:00
return Ref < Resource > ( ) ;
2020-02-28 12:27:04 +01:00
}
2024-06-28 11:16:59 +02:00
// 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 ) {
thread_load_lock . ~ MutexLock ( ) ;
2024-07-09 18:41:24 +02:00
bool exit = ! _ensure_load_progress ( ) ;
2024-06-28 11:16:59 +02:00
OS : : get_singleton ( ) - > delay_usec ( 1000 ) ;
new ( & thread_load_lock ) MutexLock ( thread_load_mutex ) ;
2024-07-09 18:41:24 +02:00
if ( exit ) {
break ;
}
2024-06-28 11:16:59 +02:00
}
}
2023-03-05 01:09:18 +01:00
res = _load_complete_inner ( * load_token , r_error , thread_load_lock ) ;
if ( load_token - > unreference ( ) ) {
memdelete ( load_token ) ;
}
2018-08-10 20:57:43 +02:00
}
2023-03-05 01:09:18 +01:00
print_lt ( " GET: user load tokens: " + itos ( user_load_tokens . size ( ) ) ) ;
2020-02-28 12:27:04 +01:00
2023-03-05 01:09:18 +01:00
return res ;
}
2020-02-28 12:27:04 +01:00
2023-03-05 01:09:18 +01:00
Ref < Resource > ResourceLoader : : _load_complete ( LoadToken & p_load_token , Error * r_error ) {
MutexLock thread_load_lock ( thread_load_mutex ) ;
return _load_complete_inner ( p_load_token , r_error , thread_load_lock ) ;
2018-08-10 20:57:43 +02:00
}
2023-03-05 01:09:18 +01:00
Ref < Resource > ResourceLoader : : _load_complete_inner ( LoadToken & p_load_token , Error * r_error , MutexLock < SafeBinaryMutex < BINARY_MUTEX_TAG > > & p_thread_load_lock ) {
2020-05-14 16:41:43 +02:00
if ( r_error ) {
2023-03-05 01:09:18 +01:00
* r_error = OK ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2023-03-05 01:09:18 +01:00
if ( ! p_load_token . local_path . is_empty ( ) ) {
if ( ! thread_load_tasks . has ( p_load_token . local_path ) ) {
# ifdef DEV_ENABLED
CRASH_NOW ( ) ;
# endif
// On non-dev, be defensive and at least avoid crashing (at this point at least).
if ( r_error ) {
* r_error = ERR_BUG ;
}
return Ref < Resource > ( ) ;
}
2014-02-10 02:10:30 +01:00
2023-03-05 01:09:18 +01:00
ThreadLoadTask & load_task = thread_load_tasks [ p_load_token . local_path ] ;
2016-05-30 01:22:00 +02:00
2023-03-05 01:09:18 +01:00
if ( load_task . status = = THREAD_LOAD_IN_PROGRESS ) {
2023-05-10 11:23:07 +02:00
DEV_ASSERT ( ( load_task . task_id = = 0 ) ! = ( load_task . thread_id = = 0 ) ) ;
if ( ( load_task . task_id ! = 0 & & load_task . task_id = = caller_task_id ) | |
( load_task . thread_id ! = 0 & & load_task . thread_id = = Thread : : get_caller_id ( ) ) ) {
2023-03-05 01:09:18 +01:00
// Load is in progress, but it's precisely this thread the one in charge.
// That means this is a cyclic load.
2020-02-28 12:27:04 +01:00
if ( r_error ) {
2023-03-05 01:09:18 +01:00
* r_error = ERR_BUSY ;
2020-02-28 12:27:04 +01:00
}
2022-05-03 01:43:50 +02:00
return Ref < Resource > ( ) ;
2020-02-28 12:27:04 +01:00
}
2019-01-27 23:24:55 +01:00
2024-06-12 13:45:31 +02:00
bool loader_is_wtp = load_task . task_id ! = 0 ;
Error wtp_task_err = FAILED ;
if ( loader_is_wtp ) {
2023-05-17 00:00:45 +02:00
// Loading thread is in the worker pool.
2024-07-08 12:19:44 +02:00
load_task . awaited = true ;
2023-05-10 11:23:07 +02:00
thread_load_mutex . unlock ( ) ;
2024-06-12 13:45:31 +02:00
wtp_task_err = WorkerThreadPool : : get_singleton ( ) - > wait_for_task_completion ( load_task . task_id ) ;
}
if ( load_task . status = = THREAD_LOAD_IN_PROGRESS ) { // If early errored, awaiting would deadlock.
if ( loader_is_wtp ) {
if ( wtp_task_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.
// CACHE_MODE_IGNORE is needed because, otherwise, the new request would just see there's
// an ongoing load for that resource and wait for it again. This value forces a new load.
Ref < ResourceLoader : : LoadToken > token = _load_start ( load_task . local_path , load_task . type_hint , LOAD_THREAD_DISTRIBUTE , ResourceFormatLoader : : CACHE_MODE_IGNORE ) ;
Ref < Resource > resource = _load_complete ( * token . ptr ( ) , & wtp_task_err ) ;
if ( r_error ) {
* r_error = wtp_task_err ;
}
thread_load_mutex . lock ( ) ;
return resource ;
} else {
DEV_ASSERT ( wtp_task_err = = OK ) ;
thread_load_mutex . lock ( ) ;
2023-05-17 00:00:45 +02:00
}
} else {
2024-06-12 13:45:31 +02:00
// Loading thread is main or user thread.
if ( ! load_task . cond_var ) {
load_task . cond_var = memnew ( ConditionVariable ) ;
}
do {
load_task . cond_var - > wait ( p_thread_load_lock ) ;
DEV_ASSERT ( thread_load_tasks . has ( p_load_token . local_path ) & & p_load_token . get_reference_count ( ) ) ;
} while ( load_task . cond_var ) ;
2023-05-17 00:00:45 +02:00
}
2023-05-10 11:23:07 +02:00
} else {
2024-06-12 13:45:31 +02:00
if ( loader_is_wtp ) {
thread_load_mutex . lock ( ) ;
2023-05-10 11:23:07 +02:00
}
}
2020-02-28 12:27:04 +01:00
}
2023-03-05 01:09:18 +01:00
if ( cleaning_tasks ) {
load_task . resource = Ref < Resource > ( ) ;
load_task . error = FAILED ;
2019-01-27 23:24:55 +01:00
}
2023-03-05 01:09:18 +01:00
Ref < Resource > resource = load_task . resource ;
if ( r_error ) {
* r_error = load_task . error ;
2020-05-14 16:41:43 +02:00
}
2023-03-05 01:09:18 +01:00
return resource ;
} else {
// Special case of an unregistered task.
// The resource should have been loaded by now.
Ref < Resource > resource = p_load_token . res_if_unregistered ;
if ( ! resource . is_valid ( ) ) {
if ( r_error ) {
* r_error = FAILED ;
}
2020-02-28 12:27:04 +01:00
}
2023-03-05 01:09:18 +01:00
return resource ;
2014-02-10 02:10:30 +01:00
}
2020-02-28 12:27:04 +01:00
}
2014-02-10 02:10:30 +01:00
2024-06-28 11:16:59 +02:00
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 ;
}
2024-06-25 14:12:40 +02:00
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 ) ;
HashMap < String , Ref < Resource > > & overrides = res_ref_overrides [ load_nesting - 1 ] ;
HashMap < String , Ref < Resource > > : : Iterator E = overrides . find ( local_path ) ;
if ( E ) {
return E - > value ;
} else {
Object * obj = ClassDB : : instantiate ( p_res_type ) ;
ERR_FAIL_NULL_V ( obj , Ref < Resource > ( ) ) ;
Ref < Resource > res ( obj ) ;
if ( ! res . is_valid ( ) ) {
memdelete ( obj ) ;
ERR_FAIL_V ( Ref < Resource > ( ) ) ;
}
overrides [ local_path ] = res ;
return res ;
}
}
Ref < Resource > ResourceLoader : : get_resource_ref_override ( const String & p_path ) {
DEV_ASSERT ( p_path = = _validate_local_path ( p_path ) ) ;
HashMap < int , HashMap < String , Ref < Resource > > > : : Iterator E = res_ref_overrides . find ( load_nesting ) ;
if ( ! E ) {
return nullptr ;
}
HashMap < String , Ref < Resource > > : : Iterator F = E - > value . find ( p_path ) ;
if ( ! F ) {
return nullptr ;
}
return F - > value ;
}
2020-02-28 12:27:04 +01:00
bool ResourceLoader : : exists ( const String & p_path , const String & p_type_hint ) {
2021-07-23 21:01:18 +02:00
String local_path = _validate_local_path ( p_path ) ;
2020-02-28 12:27:04 +01:00
if ( ResourceCache : : has ( local_path ) ) {
return true ; // If cached, it probably exists
2019-01-27 23:24:55 +01:00
}
2020-02-28 12:27:04 +01:00
bool xl_remapped = false ;
String path = _path_remap ( local_path , & xl_remapped ) ;
2019-08-15 04:57:49 +02:00
2020-02-28 12:27:04 +01:00
// Try all loaders and pick the first match for the type hint
for ( int i = 0 ; i < loader_count ; i + + ) {
if ( ! loader [ i ] - > recognize_path ( path , p_type_hint ) ) {
continue ;
}
2020-05-14 16:41:43 +02:00
if ( loader [ i ] - > exists ( path ) ) {
2020-02-28 12:27:04 +01:00
return true ;
2020-05-14 16:41:43 +02:00
}
2020-02-28 12:27:04 +01:00
}
return false ;
2014-02-10 02:10:30 +01:00
}
2018-06-11 02:59:53 +02:00
void ResourceLoader : : add_resource_format_loader ( Ref < ResourceFormatLoader > p_format_loader , bool p_at_front ) {
ERR_FAIL_COND ( p_format_loader . is_null ( ) ) ;
2014-02-10 02:10:30 +01:00
ERR_FAIL_COND ( loader_count > = MAX_LOADERS ) ;
2018-06-11 02:59:53 +02:00
2016-07-20 02:40:05 +02:00
if ( p_at_front ) {
for ( int i = loader_count ; i > 0 ; i - - ) {
loader [ i ] = loader [ i - 1 ] ;
}
loader [ 0 ] = p_format_loader ;
loader_count + + ;
} else {
loader [ loader_count + + ] = p_format_loader ;
}
2014-02-10 02:10:30 +01:00
}
2018-06-11 02:59:53 +02:00
void ResourceLoader : : remove_resource_format_loader ( Ref < ResourceFormatLoader > p_format_loader ) {
ERR_FAIL_COND ( p_format_loader . is_null ( ) ) ;
// Find loader
int i = 0 ;
for ( ; i < loader_count ; + + i ) {
2020-05-14 16:41:43 +02:00
if ( loader [ i ] = = p_format_loader ) {
2018-06-11 02:59:53 +02:00
break ;
2020-05-14 16:41:43 +02:00
}
2018-06-11 02:59:53 +02:00
}
ERR_FAIL_COND ( i > = loader_count ) ; // Not found
// Shift next loaders up
for ( ; i < loader_count - 1 ; + + i ) {
loader [ i ] = loader [ i + 1 ] ;
}
loader [ loader_count - 1 ] . unref ( ) ;
- - loader_count ;
}
2017-09-21 01:59:19 +02:00
int ResourceLoader : : get_import_order ( const String & p_path ) {
2021-07-23 21:01:18 +02:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2017-09-21 01:59:19 +02:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2017-09-21 01:59:19 +02:00
continue ;
2020-05-14 16:41:43 +02:00
}
2017-09-21 01:59:19 +02:00
return loader [ i ] - > get_import_order ( p_path ) ;
}
return 0 ;
}
2019-04-19 20:54:33 +02:00
String ResourceLoader : : get_import_group_file ( const String & p_path ) {
2021-07-23 21:01:18 +02:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2019-04-19 20:54:33 +02:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2019-04-19 20:54:33 +02:00
continue ;
2020-05-14 16:41:43 +02:00
}
2019-04-19 20:54:33 +02:00
return loader [ i ] - > get_import_group_file ( p_path ) ;
}
return String ( ) ; //not found
}
2017-08-30 00:50:58 +02:00
bool ResourceLoader : : is_import_valid ( const String & p_path ) {
2021-07-23 21:01:18 +02:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2017-08-30 00:50:58 +02:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2017-08-30 00:50:58 +02:00
continue ;
2020-05-14 16:41:43 +02:00
}
2017-08-30 00:50:58 +02:00
return loader [ i ] - > is_import_valid ( p_path ) ;
}
return false ; //not found
}
2019-03-04 15:06:15 +01:00
bool ResourceLoader : : is_imported ( const String & p_path ) {
2021-07-23 21:01:18 +02:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2019-03-04 15:06:15 +01:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2019-03-04 15:06:15 +01:00
continue ;
2020-05-14 16:41:43 +02:00
}
2019-03-04 15:06:15 +01:00
return loader [ i ] - > is_imported ( p_path ) ;
}
return false ; //not found
}
2015-08-24 01:15:56 +02:00
void ResourceLoader : : get_dependencies ( const String & p_path , List < String > * p_dependencies , bool p_add_types ) {
2021-07-23 21:01:18 +02:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2015-08-24 01:15:56 +02:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2015-08-24 01:15:56 +02:00
continue ;
2020-05-14 16:41:43 +02:00
}
2015-08-24 01:15:56 +02:00
2017-01-26 01:55:59 +01:00
loader [ i ] - > get_dependencies ( local_path , p_dependencies , p_add_types ) ;
2015-08-24 01:15:56 +02:00
}
}
2022-05-13 15:04:37 +02:00
Error ResourceLoader : : rename_dependencies ( const String & p_path , const HashMap < String , String > & p_map ) {
2021-07-23 21:01:18 +02:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2015-05-04 15:17:24 +02:00
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 16:41:43 +02:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2014-02-10 02:10:30 +01:00
continue ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2017-01-26 01:55:59 +01:00
return loader [ i ] - > rename_dependencies ( local_path , p_map ) ;
2014-02-10 02:10:30 +01:00
}
2015-08-24 01:15:56 +02:00
return OK ; // ??
2014-02-10 02:10:30 +01:00
}
2022-07-14 14:18:18 +02:00
void ResourceLoader : : get_classes_used ( const String & p_path , HashSet < StringName > * r_classes ) {
String local_path = _validate_local_path ( p_path ) ;
for ( int i = 0 ; i < loader_count ; i + + ) {
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
continue ;
}
return loader [ i ] - > get_classes_used ( p_path , r_classes ) ;
}
}
2014-02-10 02:10:30 +01:00
String ResourceLoader : : get_resource_type ( const String & p_path ) {
2021-07-23 21:01:18 +02:00
String local_path = _validate_local_path ( p_path ) ;
2015-05-04 15:17:24 +02:00
2014-02-10 02:10:30 +01:00
for ( int i = 0 ; i < loader_count ; i + + ) {
String result = loader [ i ] - > get_resource_type ( local_path ) ;
2021-12-09 10:42:46 +01:00
if ( ! result . is_empty ( ) ) {
2014-02-10 02:10:30 +01:00
return result ;
2019-10-03 22:39:08 +02:00
}
2014-02-10 02:10:30 +01:00
}
return " " ;
}
2017-06-28 22:00:18 +02:00
2023-01-19 19:12:25 +01:00
String ResourceLoader : : get_resource_script_class ( const String & p_path ) {
String local_path = _validate_local_path ( p_path ) ;
for ( int i = 0 ; i < loader_count ; i + + ) {
String result = loader [ i ] - > get_resource_script_class ( local_path ) ;
if ( ! result . is_empty ( ) ) {
return result ;
}
}
return " " ;
}
2021-07-23 21:01:18 +02:00
ResourceUID : : ID ResourceLoader : : get_resource_uid ( const String & p_path ) {
String local_path = _validate_local_path ( p_path ) ;
for ( int i = 0 ; i < loader_count ; i + + ) {
ResourceUID : : ID id = loader [ i ] - > get_resource_uid ( local_path ) ;
if ( id ! = ResourceUID : : INVALID_ID ) {
return id ;
}
}
return ResourceUID : : INVALID_ID ;
}
2017-06-28 22:00:18 +02:00
String ResourceLoader : : _path_remap ( const String & p_path , bool * r_translation_remapped ) {
2017-12-14 19:33:54 +01:00
String new_path = p_path ;
2017-06-28 22:00:18 +02:00
2019-12-04 16:50:43 +01:00
if ( translation_remaps . has ( p_path ) ) {
// translation_remaps has the following format:
2020-02-17 22:06:54 +01:00
// { "res://path.png": PackedStringArray( "res://path-ru.png:ru", "res://path-de.png:de" ) }
2019-12-04 16:50:43 +01:00
// To find the path of the remapped resource, we extract the locale name after
// the last ':' to match the project locale.
2017-12-14 19:33:54 +01:00
2022-07-29 16:30:29 +02:00
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
2017-06-28 22:00:18 +02:00
String locale = TranslationServer : : get_singleton ( ) - > get_locale ( ) ;
2019-12-04 16:50:43 +01:00
ERR_FAIL_COND_V_MSG ( locale . length ( ) < 2 , p_path , " Could not remap path ' " + p_path + " ' for translation as configured locale ' " + locale + " ' is invalid. " ) ;
2017-06-28 22:00:18 +02:00
2019-12-04 16:50:43 +01:00
Vector < String > & res_remaps = * translation_remaps . getptr ( new_path ) ;
2021-09-23 13:08:50 +02:00
int best_score = 0 ;
2019-12-04 16:50:43 +01:00
for ( int i = 0 ; i < res_remaps . size ( ) ; i + + ) {
2020-07-03 15:26:22 +02:00
int split = res_remaps [ i ] . rfind ( " : " ) ;
2019-12-04 16:50:43 +01:00
if ( split = = - 1 ) {
2017-06-28 22:00:18 +02:00
continue ;
2019-12-04 16:50:43 +01:00
}
2020-02-13 16:42:49 +01:00
String l = res_remaps [ i ] . substr ( split + 1 ) . strip_edges ( ) ;
2021-09-23 13:08:50 +02:00
int score = TranslationServer : : get_singleton ( ) - > compare_locales ( locale , l ) ;
2022-01-24 17:58:16 +01:00
if ( score > 0 & & score > = best_score ) {
2019-12-04 16:50:43 +01:00
new_path = res_remaps [ i ] . left ( split ) ;
2021-09-23 13:08:50 +02:00
best_score = score ;
if ( score = = 10 ) {
break ; // Exact match, skip the rest.
}
2019-12-04 16:50:43 +01:00
}
}
if ( r_translation_remapped ) {
* r_translation_remapped = true ;
2017-06-28 22:00:18 +02:00
}
2022-01-04 18:52:32 +01:00
// Fallback to p_path if new_path does not exist.
2023-11-12 13:16:37 +01:00
if ( ! FileAccess : : exists ( new_path + " .import " ) & & ! FileAccess : : exists ( new_path ) ) {
2022-01-04 18:52:32 +01:00
WARN_PRINT ( vformat ( " Translation remap '%s' does not exist. Falling back to '%s'. " , new_path , p_path ) ) ;
new_path = p_path ;
}
2017-06-28 22:00:18 +02:00
}
2017-12-14 19:33:54 +01:00
if ( path_remaps . has ( new_path ) ) {
new_path = path_remaps [ new_path ] ;
2022-07-29 16:30:29 +02:00
} else {
2019-12-04 16:50:43 +01:00
// Try file remap.
2024-06-11 11:46:35 +02:00
Error err ;
Ref < FileAccess > f = FileAccess : : open ( new_path + " .remap " , FileAccess : : READ , & err ) ;
if ( f . is_valid ( ) ) {
VariantParser : : StreamFile stream ;
stream . f = f ;
String assign ;
Variant value ;
VariantParser : : Tag next_tag ;
int lines = 0 ;
String error_text ;
while ( true ) {
assign = Variant ( ) ;
next_tag . fields . clear ( ) ;
next_tag . name = String ( ) ;
err = VariantParser : : parse_tag_assign_eof ( & stream , lines , error_text , next_tag , assign , value , nullptr , true ) ;
if ( err = = ERR_FILE_EOF ) {
break ;
} else if ( err ! = OK ) {
ERR_PRINT ( " Parse error: " + p_path + " .remap: " + itos ( lines ) + " error: " + error_text + " . " ) ;
break ;
}
2017-12-18 15:21:13 +01:00
2024-06-11 11:46:35 +02:00
if ( assign = = " path " ) {
new_path = value ;
break ;
} else if ( next_tag . name ! = " remap " ) {
break ;
2017-12-18 15:21:13 +01:00
}
}
}
}
2017-12-14 19:33:54 +01:00
return new_path ;
2017-06-28 22:00:18 +02:00
}
String ResourceLoader : : import_remap ( const String & p_path ) {
if ( ResourceFormatImporter : : get_singleton ( ) - > recognize_path ( p_path ) ) {
return ResourceFormatImporter : : get_singleton ( ) - > get_internal_resource_path ( p_path ) ;
}
return p_path ;
}
String ResourceLoader : : path_remap ( const String & p_path ) {
return _path_remap ( p_path ) ;
}
void ResourceLoader : : reload_translation_remaps ( ) {
2022-06-22 13:46:46 +02:00
ResourceCache : : lock . lock ( ) ;
2017-06-28 22:00:18 +02:00
List < Resource * > to_reload ;
SelfList < Resource > * E = remapped_list . first ( ) ;
while ( E ) {
to_reload . push_back ( E - > self ( ) ) ;
E = E - > next ( ) ;
}
2022-06-22 13:46:46 +02:00
ResourceCache : : lock . unlock ( ) ;
2017-06-28 22:00:18 +02:00
//now just make sure to not delete any of these resources while changing locale..
while ( to_reload . front ( ) ) {
to_reload . front ( ) - > get ( ) - > reload_from_file ( ) ;
to_reload . pop_front ( ) ;
}
}
void ResourceLoader : : load_translation_remaps ( ) {
2021-02-17 17:44:49 +01:00
if ( ! ProjectSettings : : get_singleton ( ) - > has_setting ( " internationalization/locale/translation_remaps " ) ) {
2017-07-26 15:03:13 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2017-07-26 15:03:13 +02:00
2022-10-18 16:43:37 +02:00
Dictionary remaps = GLOBAL_GET ( " internationalization/locale/translation_remaps " ) ;
2017-06-28 22:00:18 +02:00
List < Variant > keys ;
remaps . get_key_list ( & keys ) ;
2021-07-24 15:46:25 +02:00
for ( const Variant & E : keys ) {
2021-07-16 05:45:57 +02:00
Array langs = remaps [ E ] ;
2017-06-28 22:00:18 +02:00
Vector < String > lang_remaps ;
lang_remaps . resize ( langs . size ( ) ) ;
2023-12-24 13:44:21 +01:00
String * lang_remaps_ptrw = lang_remaps . ptrw ( ) ;
for ( const Variant & lang : langs ) {
* lang_remaps_ptrw + + = lang ;
2017-06-28 22:00:18 +02:00
}
2021-07-16 05:45:57 +02:00
translation_remaps [ String ( E ) ] = lang_remaps ;
2017-06-28 22:00:18 +02:00
}
}
void ResourceLoader : : clear_translation_remaps ( ) {
translation_remaps . clear ( ) ;
2020-08-30 21:44:39 +02:00
while ( remapped_list . first ( ) ! = nullptr ) {
remapped_list . remove ( remapped_list . first ( ) ) ;
}
2017-06-28 22:00:18 +02:00
}
2022-12-06 19:51:06 +01:00
void ResourceLoader : : clear_thread_load_tasks ( ) {
2023-03-05 01:09:18 +01:00
// Bring the thing down as quickly as possible without causing deadlocks or leaks.
thread_load_mutex . lock ( ) ;
cleaning_tasks = true ;
while ( true ) {
bool none_running = true ;
if ( thread_load_tasks . size ( ) ) {
for ( KeyValue < String , ResourceLoader : : ThreadLoadTask > & E : thread_load_tasks ) {
if ( E . value . status = = THREAD_LOAD_IN_PROGRESS ) {
if ( E . value . cond_var ) {
E . value . cond_var - > notify_all ( ) ;
memdelete ( E . value . cond_var ) ;
E . value . cond_var = nullptr ;
}
none_running = false ;
2022-12-06 19:51:06 +01:00
}
}
}
2023-03-05 01:09:18 +01:00
if ( none_running ) {
break ;
}
thread_load_mutex . unlock ( ) ;
OS : : get_singleton ( ) - > delay_usec ( 1000 ) ;
thread_load_mutex . lock ( ) ;
2022-12-06 19:51:06 +01:00
}
2023-03-05 01:09:18 +01:00
2023-10-22 16:59:46 +02:00
while ( user_load_tokens . begin ( ) ) {
// User load tokens remove themselves from the map on destruction.
memdelete ( user_load_tokens . begin ( ) - > value ) ;
2023-03-05 01:09:18 +01:00
}
user_load_tokens . clear ( ) ;
2022-12-06 19:51:06 +01:00
thread_load_tasks . clear ( ) ;
2023-03-05 01:09:18 +01:00
cleaning_tasks = false ;
thread_load_mutex . unlock ( ) ;
2022-12-06 19:51:06 +01:00
}
2017-12-14 19:33:54 +01:00
void ResourceLoader : : load_path_remaps ( ) {
2020-05-14 16:41:43 +02:00
if ( ! ProjectSettings : : get_singleton ( ) - > has_setting ( " path_remap/remapped_paths " ) ) {
2017-12-14 19:33:54 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2017-12-14 19:33:54 +01:00
2022-10-18 16:43:37 +02:00
Vector < String > remaps = GLOBAL_GET ( " path_remap/remapped_paths " ) ;
2017-12-14 19:33:54 +01:00
int rc = remaps . size ( ) ;
ERR_FAIL_COND ( rc & 1 ) ; //must be even
2020-02-17 22:06:54 +01:00
const String * r = remaps . ptr ( ) ;
2017-12-14 19:33:54 +01:00
for ( int i = 0 ; i < rc ; i + = 2 ) {
path_remaps [ r [ i ] ] = r [ i + 1 ] ;
}
}
void ResourceLoader : : clear_path_remaps ( ) {
path_remaps . clear ( ) ;
}
2018-10-29 20:36:31 +01:00
void ResourceLoader : : set_load_callback ( ResourceLoadedCallback p_callback ) {
_loaded_callback = p_callback ;
}
2020-04-02 01:20:12 +02:00
ResourceLoadedCallback ResourceLoader : : _loaded_callback = nullptr ;
2018-10-29 20:36:31 +01:00
2024-01-09 02:36:19 +01:00
Ref < ResourceFormatLoader > ResourceLoader : : _find_custom_resource_format_loader ( const String & path ) {
2018-06-11 02:59:53 +02:00
for ( int i = 0 ; i < loader_count ; + + i ) {
if ( loader [ i ] - > get_script_instance ( ) & & loader [ i ] - > get_script_instance ( ) - > get_script ( ) - > get_path ( ) = = path ) {
return loader [ i ] ;
}
}
return Ref < ResourceFormatLoader > ( ) ;
}
2024-01-09 02:36:19 +01:00
bool ResourceLoader : : add_custom_resource_format_loader ( const String & script_path ) {
2020-05-14 16:41:43 +02:00
if ( _find_custom_resource_format_loader ( script_path ) . is_valid ( ) ) {
2018-06-11 02:59:53 +02:00
return false ;
2020-05-14 16:41:43 +02:00
}
2018-06-11 02:59:53 +02:00
Ref < Resource > res = ResourceLoader : : load ( script_path ) ;
ERR_FAIL_COND_V ( res . is_null ( ) , false ) ;
ERR_FAIL_COND_V ( ! res - > is_class ( " Script " ) , false ) ;
Ref < Script > s = res ;
StringName ibt = s - > get_instance_base_type ( ) ;
bool valid_type = ClassDB : : is_parent_class ( ibt , " ResourceFormatLoader " ) ;
2023-11-23 16:19:24 +01:00
ERR_FAIL_COND_V_MSG ( ! valid_type , false , vformat ( " Failed to add a custom resource loader, script '%s' does not inherit 'ResourceFormatLoader'. " , script_path ) ) ;
2018-06-11 02:59:53 +02:00
2021-06-18 00:03:09 +02:00
Object * obj = ClassDB : : instantiate ( ibt ) ;
2023-11-23 16:19:24 +01:00
ERR_FAIL_NULL_V_MSG ( obj , false , vformat ( " Failed to add a custom resource loader, cannot instantiate '%s'. " , ibt ) ) ;
2018-06-11 02:59:53 +02:00
2020-11-15 15:14:06 +01:00
Ref < ResourceFormatLoader > crl = Object : : cast_to < ResourceFormatLoader > ( obj ) ;
2020-02-13 20:03:10 +01:00
crl - > set_script ( s ) ;
2018-06-11 02:59:53 +02:00
ResourceLoader : : add_resource_format_loader ( crl ) ;
return true ;
}
2022-04-28 22:49:10 +02:00
void ResourceLoader : : set_create_missing_resources_if_class_unavailable ( bool p_enable ) {
create_missing_resources_if_class_unavailable = p_enable ;
}
2018-06-11 02:59:53 +02:00
void ResourceLoader : : add_custom_loaders ( ) {
// Custom loaders registration exploits global class names
String custom_loader_base_class = ResourceFormatLoader : : get_class_static ( ) ;
List < StringName > global_classes ;
ScriptServer : : get_global_class_list ( & global_classes ) ;
2021-07-24 15:46:25 +02:00
for ( const StringName & class_name : global_classes ) {
2019-03-09 04:47:27 +01:00
StringName base_class = ScriptServer : : get_global_class_native_base ( class_name ) ;
2018-06-11 02:59:53 +02:00
if ( base_class = = custom_loader_base_class ) {
String path = ScriptServer : : get_global_class_path ( class_name ) ;
add_custom_resource_format_loader ( path ) ;
}
}
}
void ResourceLoader : : remove_custom_loaders ( ) {
2020-03-17 07:33:00 +01:00
Vector < Ref < ResourceFormatLoader > > custom_loaders ;
2018-06-11 02:59:53 +02:00
for ( int i = 0 ; i < loader_count ; + + i ) {
if ( loader [ i ] - > get_script_instance ( ) ) {
custom_loaders . push_back ( loader [ i ] ) ;
}
}
for ( int i = 0 ; i < custom_loaders . size ( ) ; + + i ) {
remove_resource_format_loader ( custom_loaders [ i ] ) ;
}
}
2023-03-05 01:09:18 +01:00
bool ResourceLoader : : is_cleaning_tasks ( ) {
MutexLock lock ( thread_load_mutex ) ;
return cleaning_tasks ;
}
2023-05-10 11:23:07 +02:00
void ResourceLoader : : initialize ( ) { }
2019-01-27 23:24:55 +01:00
2023-03-05 01:09:18 +01:00
void ResourceLoader : : finalize ( ) { }
2019-01-27 23:24:55 +01:00
2020-04-02 01:20:12 +02:00
ResourceLoadErrorNotify ResourceLoader : : err_notify = nullptr ;
DependencyErrorNotify ResourceLoader : : dep_err_notify = nullptr ;
2014-02-10 02:10:30 +01:00
2022-04-28 22:49:10 +02:00
bool ResourceLoader : : create_missing_resources_if_class_unavailable = false ;
2014-02-10 02:10:30 +01:00
bool ResourceLoader : : abort_on_missing_resource = true ;
2016-07-20 02:40:05 +02:00
bool ResourceLoader : : timestamp_on_load = false ;
2017-06-28 22:00:18 +02:00
2023-03-05 01:09:18 +01:00
thread_local int ResourceLoader : : load_nesting = 0 ;
2023-05-10 11:23:07 +02:00
thread_local WorkerThreadPool : : TaskID ResourceLoader : : caller_task_id = 0 ;
2023-07-03 13:54:55 +02:00
thread_local Vector < String > * ResourceLoader : : load_paths_stack ;
2024-06-25 14:12:40 +02:00
thread_local HashMap < int , HashMap < String , Ref < Resource > > > ResourceLoader : : res_ref_overrides ;
2023-03-05 01:09:18 +01:00
2023-02-20 19:00:26 +01:00
template < >
thread_local uint32_t SafeBinaryMutex < ResourceLoader : : BINARY_MUTEX_TAG > : : count = 0 ;
2023-03-05 01:09:18 +01:00
SafeBinaryMutex < ResourceLoader : : BINARY_MUTEX_TAG > ResourceLoader : : thread_load_mutex ;
2020-02-28 12:27:04 +01:00
HashMap < String , ResourceLoader : : ThreadLoadTask > ResourceLoader : : thread_load_tasks ;
2023-03-05 01:09:18 +01:00
bool ResourceLoader : : cleaning_tasks = false ;
HashMap < String , ResourceLoader : : LoadToken * > ResourceLoader : : user_load_tokens ;
2020-02-28 12:27:04 +01:00
2017-06-28 22:00:18 +02:00
SelfList < Resource > : : List ResourceLoader : : remapped_list ;
2020-03-17 07:33:00 +01:00
HashMap < String , Vector < String > > ResourceLoader : : translation_remaps ;
2017-12-14 19:33:54 +01:00
HashMap < String , String > ResourceLoader : : path_remaps ;
2018-10-05 04:00:02 +02:00
2020-04-02 01:20:12 +02:00
ResourceLoaderImport ResourceLoader : : import = nullptr ;