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"
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 ) ;
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.
2023-05-10 10:00:33 +02:00
CallQueue * 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-03-05 01:09:18 +01:00
if ( ! load_task . dependent_path . is_empty ( ) ) {
2023-07-03 13:54:55 +02:00
load_paths_stack - > push_back ( load_task . dependent_path ) ;
2023-03-05 01:09:18 +01:00
}
2023-05-10 10:00:33 +02:00
if ( ! Thread : : is_main_thread ( ) ) {
mq_override = memnew ( CallQueue ) ;
MessageQueue : : set_thread_singleton_override ( 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
}
2023-03-05 01:09:18 +01:00
} else {
DEV_ASSERT ( load_task . dependent_path . is_empty ( ) ) ;
2020-02-28 12:27:04 +01:00
}
2023-03-05 01:09:18 +01:00
// --
2020-02-28 12:27:04 +01:00
2023-07-03 12:27:57 +02:00
if ( ! Thread : : is_main_thread ( ) ) {
set_current_thread_safe_for_nodes ( true ) ;
}
2023-03-05 01:09:18 +01: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 10:46:49 +02:00
if ( mq_override ) {
mq_override - > flush ( ) ;
}
2023-03-05 01:09:18 +01:00
thread_load_mutex . lock ( ) ;
load_task . resource = res ;
2019-02-16 21:38:09 +01:00
2023-03-05 01:09:18 +01:00
load_task . progress = 1.0 ; //it was fully loaded at this point, so force progress to 1.0
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 ) {
if ( mq_override ) {
memdelete ( mq_override ) ;
}
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
2023-03-05 01:09:18 +01: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 12:27:04 +01:00
2023-03-05 01:09:18 +01: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 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
}
2023-03-05 01:09:18 +01: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-02 18:59:33 +02:00
}
2020-02-28 12:27:04 +01:00
2023-03-05 01:09:18 +01:00
run_on_current_thread = must_not_register | | 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 ) ;
2023-05-17 00:00:45 +02:00
if ( must_not_register ) {
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 ) {
2023-03-05 01:09:18 +01:00
MutexLock thread_load_lock ( thread_load_mutex ) ;
2014-02-10 02:10:30 +01:00
2023-03-05 01:09:18 +01: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 12:27:04 +01:00
if ( ! thread_load_tasks . has ( local_path ) ) {
2023-03-05 01:09:18 +01: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 12:27:04 +01:00
return THREAD_LOAD_INVALID_RESOURCE ;
2014-02-10 02:10:30 +01:00
}
2023-03-05 01:09:18 +01:00
2020-02-28 12:27:04 +01: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-27 23:24:55 +01:00
}
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
}
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
2023-05-17 00:00:45 +02:00
if ( load_task . task_id ! = 0 ) {
// Loading thread is in the worker pool.
2023-05-10 11:23:07 +02:00
thread_load_mutex . unlock ( ) ;
2023-05-17 00:00:45 +02:00
Error err = WorkerThreadPool : : get_singleton ( ) - > wait_for_task_completion ( load_task . task_id ) ;
if ( err = = ERR_BUSY ) {
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
// 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.
2023-05-17 00:00:45 +02:00
// 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 ( ) ;
WorkerThreadPool: Overhaul scheduling and synchronization
This commits rewrites the sync logic in a way that the
`use_system_threads_for_low_priority_tasks` setting, which was added due to
the lack of a cross-platform wait-for-multiple-objects functionality, can be
removed (it's as if it was effectively hardcoded to `false`).
With the new implementation, we have the best of both worlds: threads don't
have to poll, plus no bespoke threads are used.
In addition, regarding deadlock prevention, since not every possible case of
wait-deadlock could be avoided, this commits removes the current best-effort
avoidance mechanisms and keeps only a simple, pessimistic way of detection.
It turns out that the only current user of deadlock prevention, ResourceLoader,
works fine with it and so every possible situation in resource loading is now
properly handled, with no possibilities of deadlocking. There's a comment in
the code with further details.
Lastly, a potential for load tasks never being awaited/disposed is cleared.
2024-01-05 17:39:26 +01:00
load_task . awaited = true ;
2023-05-17 00:00:45 +02:00
}
2023-05-10 11:23:07 +02:00
} else {
2023-05-17 00:00:45 +02:00
// Loading thread is main or user thread.
2023-05-10 11:23:07 +02: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 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
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-05-27 22:40:20 +02:00
// Usually, there's no remap file and FileAccess::exists() is faster then FileAccess::open().
if ( FileAccess : : exists ( new_path + " .remap " ) ) {
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-05-27 22:40:20 +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 ;
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 ;