virtualx-engine/core/io/resource_loader.cpp
Rémi Verschelde 16f6a5b139
One Copyright Update to rule them all
As many open source projects have started doing it, we're removing the
current year from the copyright notice, so that we don't need to bump
it every year.

It seems like only the first year of publication is technically
relevant for copyright notices, and even that seems to be something
that many companies stopped listing altogether (in a version controlled
codebase, the commits are a much better source of date of publication
than a hardcoded copyright statement).

We also now list Godot Engine contributors first as we're collectively
the current maintainers of the project, and we clarify that the
"exclusive" copyright of the co-founders covers the timespan before
opensourcing (their further contributions are included as part of Godot
Engine contributors).

Also fixed "cf." Frenchism - it's meant as "refer to / see".

Backported from #70885.
2023-01-10 15:32:59 +01:00

994 lines
29 KiB
C++

/**************************************************************************/
/* 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. */
/**************************************************************************/
#include "resource_loader.h"
#include "core/io/resource_importer.h"
#include "core/os/file_access.h"
#include "core/os/os.h"
#include "core/path_remap.h"
#include "core/print_string.h"
#include "core/project_settings.h"
#include "core/translation.h"
#include "core/variant_parser.h"
Ref<ResourceFormatLoader> ResourceLoader::loader[ResourceLoader::MAX_LOADERS];
int ResourceLoader::loader_count = 0;
Error ResourceInteractiveLoader::wait() {
Error err = poll();
while (err == OK) {
err = poll();
}
return err;
}
ResourceInteractiveLoader::~ResourceInteractiveLoader() {
if (path_loading != String()) {
ResourceLoader::_remove_from_loading_map_and_thread(path_loading, path_loading_thread);
}
}
bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_for_type) const {
String extension = p_path.get_extension();
List<String> extensions;
if (p_for_type == String()) {
get_recognized_extensions(&extensions);
} else {
get_recognized_extensions_for_type(p_for_type, &extensions);
}
for (List<String>::Element *E = extensions.front(); E; E = E->next()) {
if (E->get().nocasecmp_to(extension) == 0) {
return true;
}
}
return false;
}
bool ResourceFormatLoader::handles_type(const String &p_type) const {
if (get_script_instance() && get_script_instance()->has_method("handles_type")) {
// I guess custom loaders for custom resources should use "Resource"
return get_script_instance()->call("handles_type", p_type);
}
return false;
}
String ResourceFormatLoader::get_resource_type(const String &p_path) const {
if (get_script_instance() && get_script_instance()->has_method("get_resource_type")) {
return get_script_instance()->call("get_resource_type", p_path);
}
return "";
}
void ResourceFormatLoader::get_recognized_extensions_for_type(const String &p_type, List<String> *p_extensions) const {
if (p_type == "" || handles_type(p_type)) {
get_recognized_extensions(p_extensions);
}
}
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);
}
}
void ResourceInteractiveLoader::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_resource"), &ResourceInteractiveLoader::get_resource);
ClassDB::bind_method(D_METHOD("poll"), &ResourceInteractiveLoader::poll);
ClassDB::bind_method(D_METHOD("wait"), &ResourceInteractiveLoader::wait);
ClassDB::bind_method(D_METHOD("get_stage"), &ResourceInteractiveLoader::get_stage);
ClassDB::bind_method(D_METHOD("get_stage_count"), &ResourceInteractiveLoader::get_stage_count);
}
class ResourceInteractiveLoaderDefault : public ResourceInteractiveLoader {
GDCLASS(ResourceInteractiveLoaderDefault, ResourceInteractiveLoader);
public:
Ref<Resource> resource;
virtual void set_local_path(const String &p_local_path) { /*scene->set_filename(p_local_path);*/
}
virtual Ref<Resource> get_resource() { return resource; }
virtual Error poll() { return ERR_FILE_EOF; }
virtual int get_stage() const { return 1; }
virtual int get_stage_count() const { return 1; }
virtual void set_translation_remapped(bool p_remapped) { resource->set_as_translation_remapped(p_remapped); }
ResourceInteractiveLoaderDefault() {}
};
bool ResourceFormatLoader::exists(const String &p_path) const {
return FileAccess::exists(p_path); //by default just check file
}
void ResourceFormatLoader::get_recognized_extensions(List<String> *p_extensions) const {
if (get_script_instance() && get_script_instance()->has_method("get_recognized_extensions")) {
PoolStringArray exts = get_script_instance()->call("get_recognized_extensions");
{
PoolStringArray::Read r = exts.read();
for (int i = 0; i < exts.size(); ++i) {
p_extensions->push_back(r[i]);
}
}
}
}
// Warning: Derived classes must override either `load` or `load_interactive`. The base code
// here can trigger an infinite recursion otherwise, since `load` calls `load_interactive`
// vice versa.
Ref<ResourceInteractiveLoader> ResourceFormatLoader::load_interactive(const String &p_path, const String &p_original_path, Error *r_error) {
// Warning: See previous note about the risk of infinite recursion.
Ref<Resource> res = load(p_path, p_original_path, r_error);
if (res.is_null()) {
return Ref<ResourceInteractiveLoader>();
}
Ref<ResourceInteractiveLoaderDefault> ril = Ref<ResourceInteractiveLoaderDefault>(memnew(ResourceInteractiveLoaderDefault));
ril->resource = res;
return ril;
}
RES ResourceFormatLoader::load(const String &p_path, const String &p_original_path, Error *r_error) {
// Check user-defined loader if there's any. Hard fail if it returns an error.
if (get_script_instance() && get_script_instance()->has_method("load")) {
Variant res = get_script_instance()->call("load", p_path, p_original_path);
if (res.get_type() == Variant::INT) { // Error code, abort.
if (r_error) {
*r_error = (Error)res.operator int64_t();
}
return RES();
} else { // Success, pass on result.
if (r_error) {
*r_error = OK;
}
return res;
}
}
// Warning: See previous note about the risk of infinite recursion.
Ref<ResourceInteractiveLoader> ril = load_interactive(p_path, p_original_path, r_error);
if (!ril.is_valid()) {
return RES();
}
ril->set_local_path(p_original_path);
while (true) {
Error err = ril->poll();
if (err == ERR_FILE_EOF) {
if (r_error) {
*r_error = OK;
}
return ril->get_resource();
}
if (r_error) {
*r_error = err;
}
ERR_FAIL_COND_V_MSG(err != OK, RES(), "Failed to load resource '" + p_path + "'.");
}
}
void ResourceFormatLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
if (get_script_instance() && get_script_instance()->has_method("get_dependencies")) {
PoolStringArray deps = get_script_instance()->call("get_dependencies", p_path, p_add_types);
{
PoolStringArray::Read r = deps.read();
for (int i = 0; i < deps.size(); ++i) {
p_dependencies->push_back(r[i]);
}
}
}
}
Error ResourceFormatLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) {
if (get_script_instance() && get_script_instance()->has_method("rename_dependencies")) {
Dictionary deps_dict;
for (Map<String, String>::Element *E = p_map.front(); E; E = E->next()) {
deps_dict[E->key()] = E->value();
}
int64_t res = get_script_instance()->call("rename_dependencies", deps_dict);
return (Error)res;
}
return OK;
}
void ResourceFormatLoader::_bind_methods() {
{
MethodInfo info = MethodInfo(Variant::NIL, "load", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "original_path"));
info.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT;
ClassDB::add_virtual_method(get_class_static(), info);
}
ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::POOL_STRING_ARRAY, "get_recognized_extensions"));
ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::BOOL, "handles_type", PropertyInfo(Variant::STRING, "typename")));
ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::STRING, "get_resource_type", PropertyInfo(Variant::STRING, "path")));
ClassDB::add_virtual_method(get_class_static(), MethodInfo("get_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "add_types")));
ClassDB::add_virtual_method(get_class_static(), MethodInfo(Variant::INT, "rename_dependencies", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::STRING, "renames")));
}
///////////////////////////////////
RES ResourceLoader::_load(const String &p_path, const String &p_original_path, const String &p_type_hint, bool p_no_cache, Error *r_error) {
bool found = false;
// 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(p_path, p_type_hint)) {
continue;
}
found = true;
RES res = loader[i]->load(p_path, p_original_path != String() ? p_original_path : p_path, r_error);
if (res.is_null()) {
continue;
}
return res;
}
ERR_FAIL_COND_V_MSG(found, RES(),
vformat("Failed loading resource: %s. Make sure resources have been imported by opening the project in the editor at least once.", p_path));
#ifdef TOOLS_ENABLED
FileAccessRef file_check = FileAccess::create(FileAccess::ACCESS_RESOURCES);
ERR_FAIL_COND_V_MSG(!file_check->file_exists(p_path), RES(), "Resource file not found: " + p_path + ".");
#endif
ERR_FAIL_V_MSG(RES(), "No loader found for resource: " + p_path + ".");
}
bool ResourceLoader::_add_to_loading_map(const String &p_path) {
bool success;
loading_map_mutex.lock();
LoadingMapKey key;
key.path = p_path;
key.thread = Thread::get_caller_id();
if (loading_map.has(key)) {
success = false;
} else {
loading_map[key] = true;
success = true;
}
loading_map_mutex.unlock();
return success;
}
void ResourceLoader::_remove_from_loading_map(const String &p_path) {
loading_map_mutex.lock();
LoadingMapKey key;
key.path = p_path;
key.thread = Thread::get_caller_id();
loading_map.erase(key);
loading_map_mutex.unlock();
}
void ResourceLoader::_remove_from_loading_map_and_thread(const String &p_path, Thread::ID p_thread) {
loading_map_mutex.lock();
LoadingMapKey key;
key.path = p_path;
key.thread = p_thread;
loading_map.erase(key);
loading_map_mutex.unlock();
}
RES ResourceLoader::load(const String &p_path, const String &p_type_hint, bool p_no_cache, Error *r_error) {
if (r_error) {
*r_error = ERR_CANT_OPEN;
}
String local_path;
if (p_path.is_rel_path()) {
local_path = "res://" + p_path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(p_path);
}
if (!p_no_cache) {
{
bool success = _add_to_loading_map(local_path);
ERR_FAIL_COND_V_MSG(!success, RES(), "Resource: '" + local_path + "' is already being loaded. Cyclic reference?");
}
//lock first if possible
ResourceCache::lock.read_lock();
//get ptr
Resource **rptr = ResourceCache::resources.getptr(local_path);
if (rptr) {
RES res(*rptr);
//it is possible this resource was just freed in a thread. If so, this referencing will not work and resource is considered not cached
if (res.is_valid()) {
//referencing is fine
if (r_error) {
*r_error = OK;
}
ResourceCache::lock.read_unlock();
_remove_from_loading_map(local_path);
return res;
}
}
ResourceCache::lock.read_unlock();
}
bool xl_remapped = false;
String path = _path_remap(local_path, &xl_remapped);
if (path == "") {
if (!p_no_cache) {
_remove_from_loading_map(local_path);
}
ERR_FAIL_V_MSG(RES(), "Remapping '" + local_path + "' failed.");
}
print_verbose("Loading resource: " + path);
RES res = _load(path, local_path, p_type_hint, p_no_cache, r_error);
if (res.is_null()) {
if (!p_no_cache) {
_remove_from_loading_map(local_path);
}
return RES();
}
if (!p_no_cache) {
res->set_path(local_path);
}
if (xl_remapped) {
res->set_as_translation_remapped(true);
}
#ifdef TOOLS_ENABLED
res->set_edited(false);
if (timestamp_on_load) {
uint64_t mt = FileAccess::get_modified_time(path);
//printf("mt %s: %lli\n",remapped_path.utf8().get_data(),mt);
res->set_last_modified_time(mt);
}
#endif
if (!p_no_cache) {
_remove_from_loading_map(local_path);
}
if (_loaded_callback) {
_loaded_callback(res, p_path);
}
return res;
}
bool ResourceLoader::exists(const String &p_path, const String &p_type_hint) {
String local_path;
if (p_path.is_rel_path()) {
local_path = "res://" + p_path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(p_path);
}
if (ResourceCache::has(local_path)) {
return true; // If cached, it probably exists
}
bool xl_remapped = false;
String path = _path_remap(local_path, &xl_remapped);
// 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;
}
if (loader[i]->exists(path)) {
return true;
}
}
return false;
}
Ref<ResourceInteractiveLoader> ResourceLoader::load_interactive(const String &p_path, const String &p_type_hint, bool p_no_cache, Error *r_error) {
if (r_error) {
*r_error = ERR_CANT_OPEN;
}
String local_path;
if (p_path.is_rel_path()) {
local_path = "res://" + p_path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(p_path);
}
if (!p_no_cache) {
bool success = _add_to_loading_map(local_path);
ERR_FAIL_COND_V_MSG(!success, RES(), "Resource: '" + local_path + "' is already being loaded. Cyclic reference?");
if (ResourceCache::has(local_path)) {
print_verbose("Loading resource: " + local_path + " (cached)");
Ref<Resource> res_cached = ResourceCache::get(local_path);
Ref<ResourceInteractiveLoaderDefault> ril = Ref<ResourceInteractiveLoaderDefault>(memnew(ResourceInteractiveLoaderDefault));
ril->resource = res_cached;
ril->path_loading = local_path;
ril->path_loading_thread = Thread::get_caller_id();
return ril;
}
}
bool xl_remapped = false;
String path = _path_remap(local_path, &xl_remapped);
if (path == "") {
if (!p_no_cache) {
_remove_from_loading_map(local_path);
}
ERR_FAIL_V_MSG(RES(), "Remapping '" + local_path + "' failed.");
}
print_verbose("Loading resource: " + path);
bool found = false;
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(path, p_type_hint)) {
continue;
}
found = true;
Ref<ResourceInteractiveLoader> ril = loader[i]->load_interactive(path, local_path, r_error);
if (ril.is_null()) {
continue;
}
if (!p_no_cache) {
ril->set_local_path(local_path);
ril->path_loading = local_path;
ril->path_loading_thread = Thread::get_caller_id();
}
if (xl_remapped) {
ril->set_translation_remapped(true);
}
return ril;
}
if (!p_no_cache) {
_remove_from_loading_map(local_path);
}
ERR_FAIL_COND_V_MSG(found, Ref<ResourceInteractiveLoader>(), "Failed loading resource: " + path + ".");
ERR_FAIL_V_MSG(Ref<ResourceInteractiveLoader>(), "No loader found for resource: " + path + ".");
}
void ResourceLoader::add_resource_format_loader(Ref<ResourceFormatLoader> p_format_loader, bool p_at_front) {
ERR_FAIL_COND(p_format_loader.is_null());
ERR_FAIL_COND(loader_count >= MAX_LOADERS);
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;
}
}
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) {
if (loader[i] == p_format_loader) {
break;
}
}
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;
}
int ResourceLoader::get_import_order(const String &p_path) {
String path = _path_remap(p_path);
String local_path;
if (path.is_rel_path()) {
local_path = "res://" + path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(path);
}
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(local_path)) {
continue;
}
/*
if (p_type_hint!="" && !loader[i]->handles_type(p_type_hint))
continue;
*/
return loader[i]->get_import_order(p_path);
}
return 0;
}
String ResourceLoader::get_import_group_file(const String &p_path) {
String path = _path_remap(p_path);
String local_path;
if (path.is_rel_path()) {
local_path = "res://" + path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(path);
}
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(local_path)) {
continue;
}
/*
if (p_type_hint!="" && !loader[i]->handles_type(p_type_hint))
continue;
*/
return loader[i]->get_import_group_file(p_path);
}
return String(); //not found
}
bool ResourceLoader::is_import_valid(const String &p_path) {
String path = _path_remap(p_path);
String local_path;
if (path.is_rel_path()) {
local_path = "res://" + path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(path);
}
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(local_path)) {
continue;
}
/*
if (p_type_hint!="" && !loader[i]->handles_type(p_type_hint))
continue;
*/
return loader[i]->is_import_valid(p_path);
}
return false; //not found
}
bool ResourceLoader::is_imported(const String &p_path) {
String path = _path_remap(p_path);
String local_path;
if (path.is_rel_path()) {
local_path = "res://" + path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(path);
}
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(local_path)) {
continue;
}
/*
if (p_type_hint!="" && !loader[i]->handles_type(p_type_hint))
continue;
*/
return loader[i]->is_imported(p_path);
}
return false; //not found
}
void ResourceLoader::get_dependencies(const String &p_path, List<String> *p_dependencies, bool p_add_types) {
String path = _path_remap(p_path);
String local_path;
if (path.is_rel_path()) {
local_path = "res://" + path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(path);
}
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(local_path)) {
continue;
}
/*
if (p_type_hint!="" && !loader[i]->handles_type(p_type_hint))
continue;
*/
loader[i]->get_dependencies(local_path, p_dependencies, p_add_types);
}
}
Error ResourceLoader::rename_dependencies(const String &p_path, const Map<String, String> &p_map) {
String path = _path_remap(p_path);
String local_path;
if (path.is_rel_path()) {
local_path = "res://" + path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(path);
}
for (int i = 0; i < loader_count; i++) {
if (!loader[i]->recognize_path(local_path)) {
continue;
}
/*
if (p_type_hint!="" && !loader[i]->handles_type(p_type_hint))
continue;
*/
return loader[i]->rename_dependencies(local_path, p_map);
}
return OK; // ??
}
String ResourceLoader::get_resource_type(const String &p_path) {
String local_path;
if (p_path.is_rel_path()) {
local_path = "res://" + p_path;
} else {
local_path = ProjectSettings::get_singleton()->localize_path(p_path);
}
for (int i = 0; i < loader_count; i++) {
String result = loader[i]->get_resource_type(local_path);
if (result != "") {
return result;
}
}
return "";
}
String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_remapped) {
String new_path = p_path;
if (translation_remaps.has(p_path)) {
// translation_remaps has the following format:
// { "res://path.png": PoolStringArray( "res://path-ru.png:ru", "res://path-de.png:de" ) }
// To find the path of the remapped resource, we extract the locale name after
// the last ':' to match the project locale.
// We also fall back in case of regional locales as done in TranslationServer::translate
// (e.g. 'ru_RU' -> 'ru' if the former has no specific mapping).
// An extra remap may still be necessary afterwards due to the text -> binary converter on export.
String locale = TranslationServer::get_singleton()->get_locale();
ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid.");
String lang = TranslationServer::get_language_code(locale);
Vector<String> &res_remaps = *translation_remaps.getptr(new_path);
bool near_match = false;
for (int i = 0; i < res_remaps.size(); i++) {
int split = res_remaps[i].rfind(":");
if (split == -1) {
continue;
}
String l = res_remaps[i].right(split + 1).strip_edges();
if (l == locale) { // Exact match.
new_path = res_remaps[i].left(split);
break;
} else if (near_match) {
continue; // Already found near match, keep going for potential exact match.
}
// No exact match (e.g. locale 'ru_RU' but remap is 'ru'), let's look further
// for a near match (same language code, i.e. first 2 or 3 letters before
// regional code, if included).
if (TranslationServer::get_language_code(l) == lang) {
// Language code matches, that's a near match. Keep looking for exact match.
near_match = true;
new_path = res_remaps[i].left(split);
continue;
}
}
if (r_translation_remapped) {
*r_translation_remapped = true;
}
}
if (path_remaps.has(new_path)) {
new_path = path_remaps[new_path];
} else {
// Try file remap.
Error err;
FileAccess *f = FileAccess::open(new_path + ".remap", FileAccess::READ, &err);
if (f) {
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;
}
if (assign == "path") {
new_path = value;
break;
} else if (next_tag.name != "remap") {
break;
}
}
memdelete(f);
}
}
return new_path;
}
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() {
ResourceCache::lock.read_lock();
List<Resource *> to_reload;
SelfList<Resource> *E = remapped_list.first();
while (E) {
to_reload.push_back(E->self());
E = E->next();
}
ResourceCache::lock.read_unlock();
//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() {
if (!ProjectSettings::get_singleton()->has_setting("locale/translation_remaps")) {
return;
}
Dictionary remaps = ProjectSettings::get_singleton()->get("locale/translation_remaps");
List<Variant> keys;
remaps.get_key_list(&keys);
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
Array langs = remaps[E->get()];
Vector<String> lang_remaps;
lang_remaps.resize(langs.size());
for (int i = 0; i < langs.size(); i++) {
lang_remaps.write[i] = langs[i];
}
translation_remaps[String(E->get())] = lang_remaps;
}
}
void ResourceLoader::clear_translation_remaps() {
translation_remaps.clear();
while (remapped_list.first() != nullptr) {
remapped_list.remove(remapped_list.first());
}
}
void ResourceLoader::load_path_remaps() {
if (!ProjectSettings::get_singleton()->has_setting("path_remap/remapped_paths")) {
return;
}
PoolVector<String> remaps = ProjectSettings::get_singleton()->get("path_remap/remapped_paths");
int rc = remaps.size();
ERR_FAIL_COND(rc & 1); //must be even
PoolVector<String>::Read r = remaps.read();
for (int i = 0; i < rc; i += 2) {
path_remaps[r[i]] = r[i + 1];
}
}
void ResourceLoader::clear_path_remaps() {
path_remaps.clear();
}
void ResourceLoader::set_load_callback(ResourceLoadedCallback p_callback) {
_loaded_callback = p_callback;
}
ResourceLoadedCallback ResourceLoader::_loaded_callback = nullptr;
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) {
if (_find_custom_resource_format_loader(script_path).is_valid()) {
return false;
}
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");
ERR_FAIL_COND_V_MSG(!valid_type, false, "Script does not inherit a CustomResourceLoader: " + script_path + ".");
Object *obj = ClassDB::instance(ibt);
ERR_FAIL_COND_V_MSG(obj == nullptr, false, "Cannot instance script as custom resource loader, expected 'ResourceFormatLoader' inheritance, got: " + String(ibt) + ".");
Ref<ResourceFormatLoader> crl = Object::cast_to<ResourceFormatLoader>(obj);
crl->set_script(s.get_ref_ptr());
ResourceLoader::add_resource_format_loader(crl);
return true;
}
void ResourceLoader::remove_custom_resource_format_loader(String script_path) {
Ref<ResourceFormatLoader> custom_loader = _find_custom_resource_format_loader(script_path);
if (custom_loader.is_valid()) {
remove_resource_format_loader(custom_loader);
}
}
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);
for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) {
StringName class_name = E->get();
StringName base_class = ScriptServer::get_global_class_native_base(class_name);
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() {
Vector<Ref<ResourceFormatLoader>> custom_loaders;
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]);
}
}
Mutex ResourceLoader::loading_map_mutex;
HashMap<ResourceLoader::LoadingMapKey, int, ResourceLoader::LoadingMapKeyHasher> ResourceLoader::loading_map;
void ResourceLoader::finalize() {
#ifndef NO_THREADS
const LoadingMapKey *K = nullptr;
while ((K = loading_map.next(K))) {
ERR_PRINT("Exited while resource is being loaded: " + K->path);
}
loading_map.clear();
#endif
}
ResourceLoadErrorNotify ResourceLoader::err_notify = nullptr;
void *ResourceLoader::err_notify_ud = nullptr;
DependencyErrorNotify ResourceLoader::dep_err_notify = nullptr;
void *ResourceLoader::dep_err_notify_ud = nullptr;
bool ResourceLoader::abort_on_missing_resource = true;
bool ResourceLoader::timestamp_on_load = false;
SelfList<Resource>::List ResourceLoader::remapped_list;
HashMap<String, Vector<String>> ResourceLoader::translation_remaps;
HashMap<String, String> ResourceLoader::path_remaps;
ResourceLoaderImport ResourceLoader::import = nullptr;