2014-02-10 09:10:30 +08: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 07:50:27 +08:00
2014-02-10 09:10:30 +08:00
# include "resource_loader.h"
2018-09-12 00:13:45 +08:00
2020-11-08 06:33:38 +08:00
# include "core/config/project_settings.h"
2021-06-11 20:51:48 +08:00
# include "core/io/file_access.h"
2019-02-12 20:30:56 +08:00
# include "core/io/resource_importer.h"
2023-09-07 03:02:52 +08:00
# include "core/object/script_language.h"
2023-02-21 02:00:26 +08:00
# include "core/os/condition_variable.h"
2018-09-12 00:13:45 +08:00
# include "core/os/os.h"
2020-11-08 06:33:38 +08:00
# include "core/string/print_string.h"
# include "core/string/translation.h"
# include "core/variant/variant_parser.h"
2018-09-12 00:13:45 +08:00
2020-02-28 19:27:04 +08:00
# ifdef DEBUG_LOAD_THREADED
# define print_lt(m_text) print_line(m_text)
# else
# define print_lt(m_text)
# endif
2018-06-11 08:59:53 +08:00
Ref < ResourceFormatLoader > ResourceLoader : : loader [ ResourceLoader : : MAX_LOADERS ] ;
2014-02-10 09:10:30 +08:00
int ResourceLoader : : loader_count = 0 ;
2017-01-26 08:55:59 +08:00
bool ResourceFormatLoader : : recognize_path ( const String & p_path , const String & p_for_type ) const {
2022-10-11 17:40:04 +08:00
bool ret = false ;
if ( GDVIRTUAL_CALL ( _recognize_path , p_path , p_for_type , ret ) ) {
return ret ;
}
2017-01-26 08:55:59 +08:00
String extension = p_path . get_extension ( ) ;
2016-03-09 07:00:52 +08:00
2014-02-10 09:10:30 +08:00
List < String > extensions ;
2021-12-09 17:42:46 +08:00
if ( p_for_type . is_empty ( ) ) {
2017-01-26 08:55:59 +08:00
get_recognized_extensions ( & extensions ) ;
} else {
get_recognized_extensions_for_type ( p_for_type , & extensions ) ;
}
2021-07-24 21:46:25 +08:00
for ( const String & E : extensions ) {
2021-07-16 11:45:57 +08:00
if ( E . nocasecmp_to ( extension ) = = 0 ) {
2014-02-10 09:10:30 +08:00
return true ;
2020-05-14 22:41:43 +08:00
}
2014-02-10 09:10:30 +08:00
}
2016-03-09 07:00:52 +08:00
2014-02-10 09:10:30 +08:00
return false ;
}
2018-06-11 08:59:53 +08:00
bool ResourceFormatLoader : : handles_type ( const String & p_type ) const {
2022-09-29 17:53:28 +08:00
bool success = false ;
2022-10-19 00:47:44 +08:00
GDVIRTUAL_CALL ( _handles_type , p_type , success ) ;
return success ;
2018-06-11 08:59:53 +08:00
}
2022-07-14 20:18:18 +08: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 08:59:53 +08:00
String ResourceFormatLoader : : get_resource_type ( const String & p_path ) const {
2021-08-22 09:52:44 +08:00
String ret ;
2022-10-19 00:47:44 +08:00
GDVIRTUAL_CALL ( _get_resource_type , p_path , ret ) ;
return ret ;
2018-06-11 08:59:53 +08:00
}
2023-01-20 02:12:25 +08: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-24 03:01:18 +08:00
ResourceUID : : ID ResourceFormatLoader : : get_resource_uid ( const String & p_path ) const {
2022-09-29 17:53:28 +08:00
int64_t uid = ResourceUID : : INVALID_ID ;
2022-10-19 00:47:44 +08:00
GDVIRTUAL_CALL ( _get_resource_uid , p_path , uid ) ;
return uid ;
2021-07-24 03:01:18 +08:00
}
2014-02-10 09:10:30 +08:00
void ResourceFormatLoader : : get_recognized_extensions_for_type ( const String & p_type , List < String > * p_extensions ) const {
2021-12-09 17:42:46 +08:00
if ( p_type . is_empty ( ) | | handles_type ( p_type ) ) {
2014-02-10 09:10:30 +08:00
get_recognized_extensions ( p_extensions ) ;
2020-05-14 22:41:43 +08:00
}
2014-02-10 09:10:30 +08: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-11 02:57:43 +08:00
bool ResourceFormatLoader : : exists ( const String & p_path ) const {
2022-09-29 17:53:28 +08:00
bool success = false ;
2021-08-22 09:52:44 +08:00
if ( GDVIRTUAL_CALL ( _exists , p_path , success ) ) {
return success ;
}
2022-10-19 00:47:44 +08:00
return FileAccess : : exists ( p_path ) ; // By default just check file.
2018-08-11 02:57:43 +08:00
}
2018-08-12 18:44:38 +08:00
2018-06-11 08:59:53 +08:00
void ResourceFormatLoader : : get_recognized_extensions ( List < String > * p_extensions ) const {
2021-08-22 09:52:44 +08: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 08:59:53 +08:00
}
}
}
2022-05-03 07:43:50 +08: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 09:52:44 +08:00
Variant res ;
if ( GDVIRTUAL_CALL ( _load , p_path , p_original_path , p_use_sub_threads , p_cache_mode , res ) ) {
2021-05-05 16:56:26 +08:00
if ( res . get_type ( ) = = Variant : : INT ) { // Error code, abort.
2020-05-14 22:41:43 +08:00
if ( r_error ) {
2018-06-11 08:59:53 +08:00
* r_error = ( Error ) res . operator int64_t ( ) ;
2020-05-14 22:41:43 +08:00
}
2022-05-03 07:43:50 +08:00
return Ref < Resource > ( ) ;
2021-05-05 16:56:26 +08:00
} else { // Success, pass on result.
2020-05-14 22:41:43 +08:00
if ( r_error ) {
2018-06-11 08:59:53 +08:00
* r_error = OK ;
2020-05-14 22:41:43 +08:00
}
2018-06-11 08:59:53 +08:00
return res ;
}
2014-02-10 09:10:30 +08:00
}
2020-02-28 19:27:04 +08:00
2022-05-03 07:43:50 +08: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 09:10:30 +08:00
}
2015-08-24 07:15:56 +08:00
void ResourceFormatLoader : : get_dependencies ( const String & p_path , List < String > * p_dependencies , bool p_add_types ) {
2021-08-22 09:52:44 +08: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 08:59:53 +08:00
}
}
}
2022-05-13 21:04:37 +08:00
Error ResourceFormatLoader : : rename_dependencies ( const String & p_path , const HashMap < String , String > & p_map ) {
2021-08-22 09:52:44 +08:00
Dictionary deps_dict ;
2021-08-10 04:13:42 +08:00
for ( KeyValue < String , String > E : p_map ) {
deps_dict [ E . key ] = E . value ;
2021-08-22 09:52:44 +08:00
}
2018-06-11 08:59:53 +08:00
2023-01-16 03:33:20 +08:00
Error err = OK ;
2022-10-19 00:47:44 +08:00
GDVIRTUAL_CALL ( _rename_dependencies , p_path , deps_dict , err ) ;
2023-01-16 03:33:20 +08:00
return err ;
2018-06-11 08:59:53 +08:00
}
void ResourceFormatLoader : : _bind_methods ( ) {
2021-02-19 20:35:31 +08:00
BIND_ENUM_CONSTANT ( CACHE_MODE_IGNORE ) ;
BIND_ENUM_CONSTANT ( CACHE_MODE_REUSE ) ;
BIND_ENUM_CONSTANT ( CACHE_MODE_REPLACE ) ;
2021-08-22 09:52:44 +08:00
GDVIRTUAL_BIND ( _get_recognized_extensions ) ;
2022-10-11 17:40:04 +08:00
GDVIRTUAL_BIND ( _recognize_path , " path " , " type " ) ;
2021-08-22 09:52:44 +08:00
GDVIRTUAL_BIND ( _handles_type , " type " ) ;
GDVIRTUAL_BIND ( _get_resource_type , " path " ) ;
2023-01-20 02:12:25 +08:00
GDVIRTUAL_BIND ( _get_resource_script_class , " path " ) ;
2021-08-22 09:52:44 +08: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 20:18:18 +08:00
GDVIRTUAL_BIND ( _get_classes_used , " path " ) ;
2021-08-22 09:52:44 +08:00
GDVIRTUAL_BIND ( _load , " path " , " original_path " , " use_sub_threads " , " cache_mode " ) ;
2014-02-10 09:10:30 +08:00
}
///////////////////////////////////
2023-03-05 08:09:18 +08:00
// This should be robust enough to be called redundantly without issues.
void ResourceLoader : : LoadToken : : clear ( ) {
thread_load_mutex . lock ( ) ;
2023-05-10 17:23:07 +08:00
WorkerThreadPool : : TaskID task_to_await = 0 ;
2023-03-05 08:09:18 +08: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 17:23:07 +08:00
if ( ! load_task . awaited ) {
task_to_await = load_task . task_id ;
load_task . awaited = true ;
}
2023-03-05 08:09:18 +08: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 17:23:07 +08: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 08:09:18 +08:00
}
}
ResourceLoader : : LoadToken : : ~ LoadToken ( ) {
clear ( ) ;
}
2022-05-03 07:43:50 +08: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 ) {
2023-03-05 08:09:18 +08:00
load_nesting + + ;
2023-07-03 19:54:55 +08:00
if ( load_paths_stack - > size ( ) ) {
2023-03-05 08:09:18 +08:00
thread_load_mutex . lock ( ) ;
2023-07-03 19:54:55 +08:00
HashMap < String , ThreadLoadTask > : : Iterator E = thread_load_tasks . find ( load_paths_stack - > get ( load_paths_stack - > size ( ) - 1 ) ) ;
2023-03-05 08:09:18 +08:00
if ( E ) {
E - > value . sub_tasks . insert ( p_path ) ;
}
thread_load_mutex . unlock ( ) ;
}
2023-07-03 19:54:55 +08:00
load_paths_stack - > push_back ( p_path ) ;
2017-09-01 05:57:03 +08:00
// Try all loaders and pick the first match for the type hint
2023-03-05 08:09:18 +08:00
bool found = false ;
Ref < Resource > res ;
2017-09-01 05:57:03 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
if ( ! loader [ i ] - > recognize_path ( p_path , p_type_hint ) ) {
continue ;
}
found = true ;
2023-03-05 08:09:18 +08:00
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 ) ;
if ( ! res . is_null ( ) ) {
break ;
2017-09-01 05:57:03 +08:00
}
2023-03-05 08:09:18 +08:00
}
2017-09-01 05:57:03 +08:00
2023-07-03 19:54:55 +08:00
load_paths_stack - > resize ( load_paths_stack - > size ( ) - 1 ) ;
2023-03-05 08:09:18 +08:00
load_nesting - - ;
if ( ! res . is_null ( ) ) {
2017-09-01 05:57:03 +08:00
return res ;
}
2022-05-03 07:43:50 +08:00
ERR_FAIL_COND_V_MSG ( found , Ref < Resource > ( ) ,
2020-08-23 04:19:08 +08: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 10:57:49 +08:00
2020-01-08 22:11:16 +08:00
# ifdef TOOLS_ENABLED
2022-03-23 17:08:58 +08:00
Ref < FileAccess > file_check = FileAccess : : create ( FileAccess : : ACCESS_RESOURCES ) ;
2023-07-21 10:45:34 +08: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 22:11:16 +08:00
# endif
2023-07-21 10:45:34 +08:00
ERR_FAIL_V_MSG ( Ref < Resource > ( ) , vformat ( " No loader found for resource: %s (expected type: %s) " , p_path , p_type_hint ) ) ;
2017-09-01 05:57:03 +08:00
}
2020-02-28 19:27:04 +08:00
void ResourceLoader : : _thread_load_function ( void * p_userdata ) {
ThreadLoadTask & load_task = * ( ThreadLoadTask * ) p_userdata ;
2023-05-10 17:23:07 +08: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 08:09:18 +08:00
// Thread-safe either if it's the current thread or a brand new one.
2023-05-10 16:00:33 +08:00
CallQueue * mq_override = nullptr ;
2023-05-10 17:23:07 +08:00
if ( load_nesting = = 0 ) {
2023-07-03 19:54:55 +08:00
load_paths_stack = memnew ( Vector < String > ) ;
2023-03-05 08:09:18 +08:00
if ( ! load_task . dependent_path . is_empty ( ) ) {
2023-07-03 19:54:55 +08:00
load_paths_stack - > push_back ( load_task . dependent_path ) ;
2023-03-05 08:09:18 +08:00
}
2023-05-10 16:00:33 +08:00
if ( ! Thread : : is_main_thread ( ) ) {
mq_override = memnew ( CallQueue ) ;
MessageQueue : : set_thread_singleton_override ( mq_override ) ;
2023-05-24 05:28:47 +08:00
set_current_thread_safe_for_nodes ( true ) ;
2023-05-10 16:00:33 +08:00
}
2023-03-05 08:09:18 +08:00
} else {
DEV_ASSERT ( load_task . dependent_path . is_empty ( ) ) ;
2020-02-28 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
// --
2020-02-28 19:27:04 +08:00
2023-07-03 18:27:57 +08:00
if ( ! Thread : : is_main_thread ( ) ) {
set_current_thread_safe_for_nodes ( true ) ;
}
2023-03-05 08:09:18 +08:00
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 ) ;
2023-05-19 16:46:49 +08:00
if ( mq_override ) {
mq_override - > flush ( ) ;
}
2023-03-05 08:09:18 +08:00
thread_load_mutex . lock ( ) ;
load_task . resource = res ;
2019-02-17 04:38:09 +08:00
2023-03-05 08:09:18 +08:00
load_task . progress = 1.0 ; //it was fully loaded at this point, so force progress to 1.0
2020-02-28 19:27:04 +08:00
if ( load_task . error ! = OK ) {
load_task . status = THREAD_LOAD_FAILED ;
2019-01-28 06:24:55 +08:00
} else {
2020-02-28 19:27:04 +08:00
load_task . status = THREAD_LOAD_LOADED ;
2019-01-28 06:24:55 +08:00
}
2023-03-05 08:09:18 +08:00
if ( load_task . cond_var ) {
2023-02-21 02:00:26 +08:00
load_task . cond_var - > notify_all ( ) ;
memdelete ( load_task . cond_var ) ;
load_task . cond_var = nullptr ;
2020-02-28 19:27:04 +08:00
}
2019-02-17 04:38:09 +08:00
2020-02-28 19:27:04 +08:00
if ( load_task . resource . is_valid ( ) ) {
2023-03-05 08:09:18 +08:00
if ( load_task . cache_mode ! = ResourceFormatLoader : : CACHE_MODE_IGNORE ) {
2023-10-30 06:37:54 +08:00
load_task . resource - > set_path ( load_task . local_path , load_task . cache_mode = = ResourceFormatLoader : : CACHE_MODE_REPLACE ) ;
2023-08-05 08:07:16 +08:00
} else if ( ! load_task . local_path . is_resource_file ( ) ) {
load_task . resource - > set_path_cache ( load_task . local_path ) ;
2023-03-05 08:09:18 +08:00
}
2019-02-17 04:38:09 +08:00
2020-05-14 22:41:43 +08:00
if ( load_task . xl_remapped ) {
2020-02-28 19:27:04 +08:00
load_task . resource - > set_as_translation_remapped ( true ) ;
2020-05-14 22:41:43 +08:00
}
2019-02-17 04:38:09 +08:00
2020-02-28 19:27:04 +08: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-28 06:24:55 +08:00
2020-02-28 19:27:04 +08:00
if ( _loaded_callback ) {
_loaded_callback ( load_task . resource , load_task . local_path ) ;
}
2023-10-30 06:37:54 +08:00
} else if ( load_task . cache_mode ! = ResourceFormatLoader : : CACHE_MODE_IGNORE ) {
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 19:27:04 +08:00
}
2015-08-24 07:15:56 +08:00
2023-03-05 08:09:18 +08:00
thread_load_mutex . unlock ( ) ;
2023-05-10 16:00:33 +08:00
2023-07-03 19:54:55 +08:00
if ( load_nesting = = 0 ) {
if ( mq_override ) {
memdelete ( mq_override ) ;
}
memdelete ( load_paths_stack ) ;
2023-05-10 16:00:33 +08:00
}
2020-02-28 19:27:04 +08:00
}
2020-05-14 20:29:06 +08:00
2021-07-24 03:01:18 +08: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 07:43:47 +08:00
} else if ( p_path . is_relative_path ( ) ) {
2021-07-24 03:01:18 +08:00
return " res:// " + p_path ;
2020-05-14 22:41:43 +08:00
} else {
2021-07-24 03:01:18 +08:00
return ProjectSettings : : get_singleton ( ) - > localize_path ( p_path ) ;
2020-05-14 22:41:43 +08:00
}
2021-07-24 03:01:18 +08:00
}
2014-02-10 09:10:30 +08:00
2023-03-05 08:09:18 +08: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-28 06:24:55 +08:00
2023-03-05 08:09:18 +08: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 19:27:04 +08:00
}
2014-02-10 09:10:30 +08:00
2023-03-05 08:09:18 +08: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 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
return Ref < Resource > ( ) ;
2020-02-28 19:27:04 +08:00
}
2018-09-03 00:59:33 +08:00
2023-03-05 08:09:18 +08:00
Ref < Resource > res = _load_complete ( * load_token . ptr ( ) , r_error ) ;
return res ;
}
2020-02-28 19:27:04 +08:00
2023-03-05 08:09:18 +08: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 19:27:04 +08:00
2023-03-05 08:09:18 +08:00
Ref < LoadToken > load_token ;
bool must_not_register = false ;
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 19:27:04 +08:00
2023-03-05 08:09:18 +08:00
if ( thread_load_tasks . has ( local_path ) ) {
load_token = Ref < LoadToken > ( thread_load_tasks [ local_path ] . load_token ) ;
if ( ! load_token . is_valid ( ) ) {
// 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 ( ) ;
} else {
if ( p_cache_mode ! = ResourceFormatLoader : : CACHE_MODE_IGNORE ) {
return load_token ;
}
2020-02-28 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
}
2022-06-22 19:46:46 +08:00
2023-03-05 08:09:18 +08:00
load_token . instantiate ( ) ;
load_token - > local_path = local_path ;
2022-06-22 19:46:46 +08:00
2023-03-05 08:09:18 +08: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 ;
2023-10-30 06:37:54 +08:00
if ( p_cache_mode = = ResourceFormatLoader : : CACHE_MODE_REUSE ) {
2023-03-05 08:09:18 +08: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 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
// 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.
must_not_register = thread_load_tasks . has ( local_path ) & & p_cache_mode = = ResourceFormatLoader : : CACHE_MODE_IGNORE ;
if ( must_not_register ) {
load_token - > local_path . clear ( ) ;
unregistered_load_task = load_task ;
} else {
thread_load_tasks [ local_path ] = load_task ;
}
load_task_ptr = must_not_register ? & unregistered_load_task : & thread_load_tasks [ local_path ] ;
2018-09-03 00:59:33 +08:00
}
2020-02-28 19:27:04 +08:00
2023-03-05 08:09:18 +08:00
run_on_current_thread = must_not_register | | p_thread_mode = = LOAD_THREAD_FROM_CURRENT ;
2017-12-15 02:33:54 +08:00
2023-03-05 08:09:18 +08:00
if ( run_on_current_thread ) {
2023-05-10 17:23:07 +08:00
load_task_ptr - > thread_id = Thread : : get_caller_id ( ) ;
2023-03-05 08:09:18 +08:00
} else {
2023-05-10 17:23:07 +08:00
load_task_ptr - > task_id = WorkerThreadPool : : get_singleton ( ) - > add_native_task ( & ResourceLoader : : _thread_load_function , load_task_ptr ) ;
2023-03-05 08:09:18 +08:00
}
2019-01-28 06:24:55 +08:00
}
2017-12-15 02:33:54 +08:00
2023-03-05 08:09:18 +08:00
if ( run_on_current_thread ) {
_thread_load_function ( load_task_ptr ) ;
2023-05-17 06:00:45 +08:00
if ( must_not_register ) {
load_token - > res_if_unregistered = load_task_ptr - > resource ;
}
2023-03-05 08:09:18 +08:00
}
2020-02-28 19:27:04 +08:00
2023-03-05 08:09:18 +08:00
return load_token ;
2020-02-28 19:27:04 +08: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 ] ;
int dep_count = load_task . sub_tasks . size ( ) ;
if ( dep_count > 0 ) {
float dep_progress = 0 ;
2022-05-19 07:43:40 +08:00
for ( const String & E : load_task . sub_tasks ) {
dep_progress + = _dependency_get_progress ( E ) ;
2020-02-28 19:27:04 +08:00
}
dep_progress / = float ( dep_count ) ;
dep_progress * = 0.5 ;
dep_progress + = load_task . progress * 0.5 ;
return dep_progress ;
} else {
return load_task . progress ;
2019-01-28 06:24:55 +08:00
}
2020-02-28 19:27:04 +08:00
} else {
return 1.0 ; //assume finished loading it so it no longer exists
2017-09-01 05:57:03 +08:00
}
2020-02-28 19:27:04 +08:00
}
2017-06-29 04:00:18 +08:00
2020-02-28 19:27:04 +08:00
ResourceLoader : : ThreadLoadStatus ResourceLoader : : load_threaded_get_status ( const String & p_path , float * r_progress ) {
2023-03-05 08:09:18 +08:00
MutexLock thread_load_lock ( thread_load_mutex ) ;
2014-02-10 09:10:30 +08:00
2023-03-05 08:09:18 +08: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 ;
}
String local_path = _validate_local_path ( p_path ) ;
2020-02-28 19:27:04 +08:00
if ( ! thread_load_tasks . has ( local_path ) ) {
2023-03-05 08:09:18 +08:00
# ifdef DEV_ENABLED
CRASH_NOW ( ) ;
# endif
// On non-dev, be defensive and at least avoid crashing (at this point at least).
2020-02-28 19:27:04 +08:00
return THREAD_LOAD_INVALID_RESOURCE ;
2014-02-10 09:10:30 +08:00
}
2023-03-05 08:09:18 +08:00
2020-02-28 19:27:04 +08:00
ThreadLoadTask & load_task = thread_load_tasks [ local_path ] ;
ThreadLoadStatus status ;
status = load_task . status ;
if ( r_progress ) {
* r_progress = _dependency_get_progress ( local_path ) ;
2019-01-28 06:24:55 +08:00
}
2020-02-28 19:27:04 +08:00
return status ;
2014-02-10 09:10:30 +08:00
}
2020-05-14 20:29:06 +08:00
2022-05-03 07:43:50 +08:00
Ref < Resource > ResourceLoader : : load_threaded_get ( const String & p_path , Error * r_error ) {
2023-03-05 08:09:18 +08:00
if ( r_error ) {
* r_error = OK ;
2018-08-11 02:57:43 +08:00
}
2023-03-05 08:09:18 +08:00
Ref < Resource > res ;
{
MutexLock thread_load_lock ( thread_load_mutex ) ;
2018-08-11 02:57:43 +08:00
2023-03-05 08:09:18 +08: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-27 00:17:41 +08:00
if ( r_error ) {
2023-03-05 08:09:18 +08:00
* r_error = ERR_INVALID_PARAMETER ;
2023-02-27 00:17:41 +08:00
}
return Ref < Resource > ( ) ;
2018-08-11 02:57:43 +08:00
}
2023-03-05 08:09:18 +08: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 19:27:04 +08:00
if ( r_error ) {
2023-03-05 08:09:18 +08:00
* r_error = ERR_BUSY ;
2020-02-28 19:27:04 +08:00
}
2022-05-03 07:43:50 +08:00
return Ref < Resource > ( ) ;
2020-02-28 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
res = _load_complete_inner ( * load_token , r_error , thread_load_lock ) ;
if ( load_token - > unreference ( ) ) {
memdelete ( load_token ) ;
}
2018-08-11 02:57:43 +08:00
}
2023-03-05 08:09:18 +08:00
print_lt ( " GET: user load tokens: " + itos ( user_load_tokens . size ( ) ) ) ;
2020-02-28 19:27:04 +08:00
2023-03-05 08:09:18 +08:00
return res ;
}
2020-02-28 19:27:04 +08:00
2023-03-05 08:09:18 +08: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-11 02:57:43 +08:00
}
2023-03-05 08:09:18 +08: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 22:41:43 +08:00
if ( r_error ) {
2023-03-05 08:09:18 +08:00
* r_error = OK ;
2020-05-14 22:41:43 +08:00
}
2014-02-10 09:10:30 +08:00
2023-03-05 08:09:18 +08: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 09:10:30 +08:00
2023-03-05 08:09:18 +08:00
ThreadLoadTask & load_task = thread_load_tasks [ p_load_token . local_path ] ;
2016-05-30 07:22:00 +08:00
2023-03-05 08:09:18 +08:00
if ( load_task . status = = THREAD_LOAD_IN_PROGRESS ) {
2023-05-10 17:23:07 +08: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 08:09:18 +08: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 19:27:04 +08:00
if ( r_error ) {
2023-03-05 08:09:18 +08:00
* r_error = ERR_BUSY ;
2020-02-28 19:27:04 +08:00
}
2022-05-03 07:43:50 +08:00
return Ref < Resource > ( ) ;
2020-02-28 19:27:04 +08:00
}
2019-01-28 06:24:55 +08:00
2023-05-17 06:00:45 +08:00
if ( load_task . task_id ! = 0 ) {
// Loading thread is in the worker pool.
2023-05-10 17:23:07 +08:00
load_task . awaited = true ;
thread_load_mutex . unlock ( ) ;
2023-05-17 06:00:45 +08:00
Error err = WorkerThreadPool : : get_singleton ( ) - > wait_for_task_completion ( load_task . task_id ) ;
if ( err = = ERR_BUSY ) {
// The WorkerThreadPool has scheduled tasks in a way that the current load depends on
// another one in a lower stack frame. Restart such load here. When the stack is eventually
// unrolled, the original load will have been notified to go on.
# ifdef DEV_ENABLED
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. " ) ;
# endif
// 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 ( ) , & err ) ;
if ( r_error ) {
* r_error = err ;
}
thread_load_mutex . lock ( ) ;
return resource ;
} else {
DEV_ASSERT ( err = = OK ) ;
thread_load_mutex . lock ( ) ;
}
2023-05-10 17:23:07 +08:00
} else {
2023-05-17 06:00:45 +08:00
// Loading thread is main or user thread.
2023-05-10 17:23:07 +08:00
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 ) ;
}
2020-02-28 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
if ( cleaning_tasks ) {
load_task . resource = Ref < Resource > ( ) ;
load_task . error = FAILED ;
2019-01-28 06:24:55 +08:00
}
2023-03-05 08:09:18 +08:00
Ref < Resource > resource = load_task . resource ;
if ( r_error ) {
* r_error = load_task . error ;
2020-05-14 22:41:43 +08:00
}
2023-03-05 08:09:18 +08: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 19:27:04 +08:00
}
2023-03-05 08:09:18 +08:00
return resource ;
2014-02-10 09:10:30 +08:00
}
2020-02-28 19:27:04 +08:00
}
2014-02-10 09:10:30 +08:00
2020-02-28 19:27:04 +08:00
bool ResourceLoader : : exists ( const String & p_path , const String & p_type_hint ) {
2021-07-24 03:01:18 +08:00
String local_path = _validate_local_path ( p_path ) ;
2020-02-28 19:27:04 +08:00
if ( ResourceCache : : has ( local_path ) ) {
return true ; // If cached, it probably exists
2019-01-28 06:24:55 +08:00
}
2020-02-28 19:27:04 +08:00
bool xl_remapped = false ;
String path = _path_remap ( local_path , & xl_remapped ) ;
2019-08-15 10:57:49 +08:00
2020-02-28 19:27:04 +08: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 22:41:43 +08:00
if ( loader [ i ] - > exists ( path ) ) {
2020-02-28 19:27:04 +08:00
return true ;
2020-05-14 22:41:43 +08:00
}
2020-02-28 19:27:04 +08:00
}
return false ;
2014-02-10 09:10:30 +08:00
}
2018-06-11 08:59:53 +08: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 09:10:30 +08:00
ERR_FAIL_COND ( loader_count > = MAX_LOADERS ) ;
2018-06-11 08:59:53 +08:00
2016-07-20 08:40:05 +08: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 09:10:30 +08:00
}
2018-06-11 08:59:53 +08: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 22:41:43 +08:00
if ( loader [ i ] = = p_format_loader ) {
2018-06-11 08:59:53 +08:00
break ;
2020-05-14 22:41:43 +08:00
}
2018-06-11 08:59:53 +08: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 07:59:19 +08:00
int ResourceLoader : : get_import_order ( const String & p_path ) {
2021-07-24 03:01:18 +08:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2017-09-21 07:59:19 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 22:41:43 +08:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2017-09-21 07:59:19 +08:00
continue ;
2020-05-14 22:41:43 +08:00
}
2017-09-21 07:59:19 +08:00
return loader [ i ] - > get_import_order ( p_path ) ;
}
return 0 ;
}
2019-04-20 02:54:33 +08:00
String ResourceLoader : : get_import_group_file ( const String & p_path ) {
2021-07-24 03:01:18 +08:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2019-04-20 02:54:33 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 22:41:43 +08:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2019-04-20 02:54:33 +08:00
continue ;
2020-05-14 22:41:43 +08:00
}
2019-04-20 02:54:33 +08:00
return loader [ i ] - > get_import_group_file ( p_path ) ;
}
return String ( ) ; //not found
}
2017-08-30 06:50:58 +08:00
bool ResourceLoader : : is_import_valid ( const String & p_path ) {
2021-07-24 03:01:18 +08:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2017-08-30 06:50:58 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 22:41:43 +08:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2017-08-30 06:50:58 +08:00
continue ;
2020-05-14 22:41:43 +08:00
}
2017-08-30 06:50:58 +08:00
return loader [ i ] - > is_import_valid ( p_path ) ;
}
return false ; //not found
}
2019-03-04 22:06:15 +08:00
bool ResourceLoader : : is_imported ( const String & p_path ) {
2021-07-24 03:01:18 +08:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2019-03-04 22:06:15 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 22:41:43 +08:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2019-03-04 22:06:15 +08:00
continue ;
2020-05-14 22:41:43 +08:00
}
2019-03-04 22:06:15 +08:00
return loader [ i ] - > is_imported ( p_path ) ;
}
return false ; //not found
}
2015-08-24 07:15:56 +08:00
void ResourceLoader : : get_dependencies ( const String & p_path , List < String > * p_dependencies , bool p_add_types ) {
2021-07-24 03:01:18 +08:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2015-08-24 07:15:56 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 22:41:43 +08:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2015-08-24 07:15:56 +08:00
continue ;
2020-05-14 22:41:43 +08:00
}
2015-08-24 07:15:56 +08:00
2017-01-26 08:55:59 +08:00
loader [ i ] - > get_dependencies ( local_path , p_dependencies , p_add_types ) ;
2015-08-24 07:15:56 +08:00
}
}
2022-05-13 21:04:37 +08:00
Error ResourceLoader : : rename_dependencies ( const String & p_path , const HashMap < String , String > & p_map ) {
2021-07-24 03:01:18 +08:00
String local_path = _path_remap ( _validate_local_path ( p_path ) ) ;
2015-05-04 21:17:24 +08:00
2014-02-10 09:10:30 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
2020-05-14 22:41:43 +08:00
if ( ! loader [ i ] - > recognize_path ( local_path ) ) {
2014-02-10 09:10:30 +08:00
continue ;
2020-05-14 22:41:43 +08:00
}
2014-02-10 09:10:30 +08:00
2017-01-26 08:55:59 +08:00
return loader [ i ] - > rename_dependencies ( local_path , p_map ) ;
2014-02-10 09:10:30 +08:00
}
2015-08-24 07:15:56 +08:00
return OK ; // ??
2014-02-10 09:10:30 +08:00
}
2022-07-14 20:18:18 +08: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 09:10:30 +08:00
String ResourceLoader : : get_resource_type ( const String & p_path ) {
2021-07-24 03:01:18 +08:00
String local_path = _validate_local_path ( p_path ) ;
2015-05-04 21:17:24 +08:00
2014-02-10 09:10:30 +08:00
for ( int i = 0 ; i < loader_count ; i + + ) {
String result = loader [ i ] - > get_resource_type ( local_path ) ;
2021-12-09 17:42:46 +08:00
if ( ! result . is_empty ( ) ) {
2014-02-10 09:10:30 +08:00
return result ;
2019-10-04 04:39:08 +08:00
}
2014-02-10 09:10:30 +08:00
}
return " " ;
}
2017-06-29 04:00:18 +08:00
2023-01-20 02:12:25 +08: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-24 03:01:18 +08: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-29 04:00:18 +08:00
String ResourceLoader : : _path_remap ( const String & p_path , bool * r_translation_remapped ) {
2017-12-15 02:33:54 +08:00
String new_path = p_path ;
2017-06-29 04:00:18 +08:00
2019-12-04 23:50:43 +08:00
if ( translation_remaps . has ( p_path ) ) {
// translation_remaps has the following format:
2020-02-18 05:06:54 +08:00
// { "res://path.png": PackedStringArray( "res://path-ru.png:ru", "res://path-de.png:de" ) }
2019-12-04 23:50:43 +08:00
// To find the path of the remapped resource, we extract the locale name after
// the last ':' to match the project locale.
2017-12-15 02:33:54 +08:00
2022-07-29 22:30:29 +08:00
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
2017-06-29 04:00:18 +08:00
String locale = TranslationServer : : get_singleton ( ) - > get_locale ( ) ;
2019-12-04 23:50:43 +08: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-29 04:00:18 +08:00
2019-12-04 23:50:43 +08:00
Vector < String > & res_remaps = * translation_remaps . getptr ( new_path ) ;
2021-09-23 19:08:50 +08:00
int best_score = 0 ;
2019-12-04 23:50:43 +08:00
for ( int i = 0 ; i < res_remaps . size ( ) ; i + + ) {
2020-07-03 21:26:22 +08:00
int split = res_remaps [ i ] . rfind ( " : " ) ;
2019-12-04 23:50:43 +08:00
if ( split = = - 1 ) {
2017-06-29 04:00:18 +08:00
continue ;
2019-12-04 23:50:43 +08:00
}
2020-02-13 23:42:49 +08:00
String l = res_remaps [ i ] . substr ( split + 1 ) . strip_edges ( ) ;
2021-09-23 19:08:50 +08:00
int score = TranslationServer : : get_singleton ( ) - > compare_locales ( locale , l ) ;
2022-01-25 00:58:16 +08:00
if ( score > 0 & & score > = best_score ) {
2019-12-04 23:50:43 +08:00
new_path = res_remaps [ i ] . left ( split ) ;
2021-09-23 19:08:50 +08:00
best_score = score ;
if ( score = = 10 ) {
break ; // Exact match, skip the rest.
}
2019-12-04 23:50:43 +08:00
}
}
if ( r_translation_remapped ) {
* r_translation_remapped = true ;
2017-06-29 04:00:18 +08:00
}
2022-01-05 01:52:32 +08:00
// Fallback to p_path if new_path does not exist.
2023-11-12 20:16:37 +08:00
if ( ! FileAccess : : exists ( new_path + " .import " ) & & ! FileAccess : : exists ( new_path ) ) {
2022-01-05 01:52:32 +08:00
WARN_PRINT ( vformat ( " Translation remap '%s' does not exist. Falling back to '%s'. " , new_path , p_path ) ) ;
new_path = p_path ;
}
2017-06-29 04:00:18 +08:00
}
2017-12-15 02:33:54 +08:00
if ( path_remaps . has ( new_path ) ) {
new_path = path_remaps [ new_path ] ;
2022-07-29 22:30:29 +08:00
} else {
2019-12-04 23:50:43 +08:00
// Try file remap.
2017-12-18 22:21:13 +08:00
Error err ;
2022-07-29 22:30:29 +08:00
Ref < FileAccess > f = FileAccess : : open ( new_path + " .remap " , FileAccess : : READ , & err ) ;
2022-03-23 17:08:58 +08:00
if ( f . is_valid ( ) ) {
2017-12-18 22:21:13 +08:00
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 ( ) ;
2020-04-02 07:20:12 +08:00
err = VariantParser : : parse_tag_assign_eof ( & stream , lines , error_text , next_tag , assign , value , nullptr , true ) ;
2017-12-18 22:21:13 +08:00
if ( err = = ERR_FILE_EOF ) {
break ;
} else if ( err ! = OK ) {
2019-11-07 00:03:04 +08:00
ERR_PRINT ( " Parse error: " + p_path + " .remap: " + itos ( lines ) + " error: " + error_text + " . " ) ;
2017-12-18 22:21:13 +08:00
break ;
}
if ( assign = = " path " ) {
new_path = value ;
break ;
} else if ( next_tag . name ! = " remap " ) {
break ;
}
}
}
}
2017-12-15 02:33:54 +08:00
return new_path ;
2017-06-29 04:00:18 +08: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 19:46:46 +08:00
ResourceCache : : lock . lock ( ) ;
2017-06-29 04:00:18 +08: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 19:46:46 +08:00
ResourceCache : : lock . unlock ( ) ;
2017-06-29 04:00:18 +08: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-18 00:44:49 +08:00
if ( ! ProjectSettings : : get_singleton ( ) - > has_setting ( " internationalization/locale/translation_remaps " ) ) {
2017-07-26 21:03:13 +08:00
return ;
2020-05-14 22:41:43 +08:00
}
2017-07-26 21:03:13 +08:00
2022-10-18 22:43:37 +08:00
Dictionary remaps = GLOBAL_GET ( " internationalization/locale/translation_remaps " ) ;
2017-06-29 04:00:18 +08:00
List < Variant > keys ;
remaps . get_key_list ( & keys ) ;
2021-07-24 21:46:25 +08:00
for ( const Variant & E : keys ) {
2021-07-16 11:45:57 +08:00
Array langs = remaps [ E ] ;
2017-06-29 04:00:18 +08:00
Vector < String > lang_remaps ;
lang_remaps . resize ( langs . size ( ) ) ;
for ( int i = 0 ; i < langs . size ( ) ; i + + ) {
2018-07-25 09:11:03 +08:00
lang_remaps . write [ i ] = langs [ i ] ;
2017-06-29 04:00:18 +08:00
}
2021-07-16 11:45:57 +08:00
translation_remaps [ String ( E ) ] = lang_remaps ;
2017-06-29 04:00:18 +08:00
}
}
void ResourceLoader : : clear_translation_remaps ( ) {
translation_remaps . clear ( ) ;
2020-08-31 03:44:39 +08:00
while ( remapped_list . first ( ) ! = nullptr ) {
remapped_list . remove ( remapped_list . first ( ) ) ;
}
2017-06-29 04:00:18 +08:00
}
2022-12-07 02:51:06 +08:00
void ResourceLoader : : clear_thread_load_tasks ( ) {
2023-03-05 08:09:18 +08: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-07 02:51:06 +08:00
}
}
}
2023-03-05 08:09:18 +08:00
if ( none_running ) {
break ;
}
thread_load_mutex . unlock ( ) ;
OS : : get_singleton ( ) - > delay_usec ( 1000 ) ;
thread_load_mutex . lock ( ) ;
2022-12-07 02:51:06 +08:00
}
2023-03-05 08:09:18 +08:00
2023-10-22 22:59:46 +08: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 08:09:18 +08:00
}
user_load_tokens . clear ( ) ;
2022-12-07 02:51:06 +08:00
thread_load_tasks . clear ( ) ;
2023-03-05 08:09:18 +08:00
cleaning_tasks = false ;
thread_load_mutex . unlock ( ) ;
2022-12-07 02:51:06 +08:00
}
2017-12-15 02:33:54 +08:00
void ResourceLoader : : load_path_remaps ( ) {
2020-05-14 22:41:43 +08:00
if ( ! ProjectSettings : : get_singleton ( ) - > has_setting ( " path_remap/remapped_paths " ) ) {
2017-12-15 02:33:54 +08:00
return ;
2020-05-14 22:41:43 +08:00
}
2017-12-15 02:33:54 +08:00
2022-10-18 22:43:37 +08:00
Vector < String > remaps = GLOBAL_GET ( " path_remap/remapped_paths " ) ;
2017-12-15 02:33:54 +08:00
int rc = remaps . size ( ) ;
ERR_FAIL_COND ( rc & 1 ) ; //must be even
2020-02-18 05:06:54 +08:00
const String * r = remaps . ptr ( ) ;
2017-12-15 02:33:54 +08: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-30 03:36:31 +08:00
void ResourceLoader : : set_load_callback ( ResourceLoadedCallback p_callback ) {
_loaded_callback = p_callback ;
}
2020-04-02 07:20:12 +08:00
ResourceLoadedCallback ResourceLoader : : _loaded_callback = nullptr ;
2018-10-30 03:36:31 +08:00
2018-06-11 08:59:53 +08:00
Ref < ResourceFormatLoader > ResourceLoader : : _find_custom_resource_format_loader ( String path ) {
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 > ( ) ;
}
bool ResourceLoader : : add_custom_resource_format_loader ( String script_path ) {
2020-05-14 22:41:43 +08:00
if ( _find_custom_resource_format_loader ( script_path ) . is_valid ( ) ) {
2018-06-11 08:59:53 +08:00
return false ;
2020-05-14 22:41:43 +08:00
}
2018-06-11 08:59:53 +08: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 23:19:24 +08: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 08:59:53 +08:00
2021-06-18 06:03:09 +08:00
Object * obj = ClassDB : : instantiate ( ibt ) ;
2023-11-23 23:19:24 +08:00
ERR_FAIL_NULL_V_MSG ( obj , false , vformat ( " Failed to add a custom resource loader, cannot instantiate '%s'. " , ibt ) ) ;
2018-06-11 08:59:53 +08:00
2020-11-15 22:14:06 +08:00
Ref < ResourceFormatLoader > crl = Object : : cast_to < ResourceFormatLoader > ( obj ) ;
2020-02-14 03:03:10 +08:00
crl - > set_script ( s ) ;
2018-06-11 08:59:53 +08:00
ResourceLoader : : add_resource_format_loader ( crl ) ;
return true ;
}
2022-04-29 04:49:10 +08:00
void ResourceLoader : : set_create_missing_resources_if_class_unavailable ( bool p_enable ) {
create_missing_resources_if_class_unavailable = p_enable ;
}
2018-06-11 08:59:53 +08: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 21:46:25 +08:00
for ( const StringName & class_name : global_classes ) {
2019-03-09 11:47:27 +08:00
StringName base_class = ScriptServer : : get_global_class_native_base ( class_name ) ;
2018-06-11 08:59:53 +08: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 14:33:00 +08:00
Vector < Ref < ResourceFormatLoader > > custom_loaders ;
2018-06-11 08:59:53 +08: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 08:09:18 +08:00
bool ResourceLoader : : is_cleaning_tasks ( ) {
MutexLock lock ( thread_load_mutex ) ;
return cleaning_tasks ;
}
2023-05-10 17:23:07 +08:00
void ResourceLoader : : initialize ( ) { }
2019-01-28 06:24:55 +08:00
2023-03-05 08:09:18 +08:00
void ResourceLoader : : finalize ( ) { }
2019-01-28 06:24:55 +08:00
2020-04-02 07:20:12 +08:00
ResourceLoadErrorNotify ResourceLoader : : err_notify = nullptr ;
DependencyErrorNotify ResourceLoader : : dep_err_notify = nullptr ;
2014-02-10 09:10:30 +08:00
2022-04-29 04:49:10 +08:00
bool ResourceLoader : : create_missing_resources_if_class_unavailable = false ;
2014-02-10 09:10:30 +08:00
bool ResourceLoader : : abort_on_missing_resource = true ;
2016-07-20 08:40:05 +08:00
bool ResourceLoader : : timestamp_on_load = false ;
2017-06-29 04:00:18 +08:00
2023-03-05 08:09:18 +08:00
thread_local int ResourceLoader : : load_nesting = 0 ;
2023-05-10 17:23:07 +08:00
thread_local WorkerThreadPool : : TaskID ResourceLoader : : caller_task_id = 0 ;
2023-07-03 19:54:55 +08:00
thread_local Vector < String > * ResourceLoader : : load_paths_stack ;
2023-03-05 08:09:18 +08:00
2023-02-21 02:00:26 +08:00
template < >
thread_local uint32_t SafeBinaryMutex < ResourceLoader : : BINARY_MUTEX_TAG > : : count = 0 ;
2023-03-05 08:09:18 +08:00
SafeBinaryMutex < ResourceLoader : : BINARY_MUTEX_TAG > ResourceLoader : : thread_load_mutex ;
2020-02-28 19:27:04 +08:00
HashMap < String , ResourceLoader : : ThreadLoadTask > ResourceLoader : : thread_load_tasks ;
2023-03-05 08:09:18 +08:00
bool ResourceLoader : : cleaning_tasks = false ;
HashMap < String , ResourceLoader : : LoadToken * > ResourceLoader : : user_load_tokens ;
2020-02-28 19:27:04 +08:00
2017-06-29 04:00:18 +08:00
SelfList < Resource > : : List ResourceLoader : : remapped_list ;
2020-03-17 14:33:00 +08:00
HashMap < String , Vector < String > > ResourceLoader : : translation_remaps ;
2017-12-15 02:33:54 +08:00
HashMap < String , String > ResourceLoader : : path_remaps ;
2018-10-05 10:00:02 +08:00
2020-04-02 07:20:12 +08:00
ResourceLoaderImport ResourceLoader : : import = nullptr ;