/*************************************************************************/ /* resource_loader.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* 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_import.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" ResourceFormatLoader *ResourceLoader::loader[MAX_LOADERS]; int ResourceLoader::loader_count = 0; Error ResourceInteractiveLoader::wait() { Error err = poll(); while (err == OK) { err = poll(); } return err; } bool ResourceFormatLoader::recognize_path(const String &p_path, const String &p_for_type) const { String extension = p_path.get_extension(); List extensions; if (p_for_type == String()) { get_recognized_extensions(&extensions); } else { get_recognized_extensions_for_type(p_for_type, &extensions); } for (List::Element *E = extensions.front(); E; E = E->next()) { if (E->get().nocasecmp_to(extension) == 0) return true; } return false; } void ResourceFormatLoader::get_recognized_extensions_for_type(const String &p_type, List *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 *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; virtual void set_local_path(const String &p_local_path) { /*scene->set_filename(p_local_path);*/ } virtual Ref 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() {} }; Ref ResourceFormatLoader::load_interactive(const String &p_path, const String &p_original_path, Error *r_error) { //either this Ref res = load(p_path, p_original_path, r_error); if (res.is_null()) return Ref(); Ref ril = Ref(memnew(ResourceInteractiveLoaderDefault)); ril->resource = res; return ril; } bool ResourceFormatLoader::exists(const String &p_path) const { return FileAccess::exists(p_path); //by default just check file } RES ResourceFormatLoader::load(const String &p_path, const String &p_original_path, Error *r_error) { String path = p_path; //or this must be implemented Ref 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(err != OK, RES()); } return RES(); } void ResourceFormatLoader::get_dependencies(const String &p_path, List *p_dependencies, bool p_add_types) { //do nothing by default } /////////////////////////////////// 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; } if (found) { ERR_EXPLAIN("Failed loading resource: " + p_path); } else { ERR_EXPLAIN("No loader found for resource: " + p_path); } ERR_FAIL_V(RES()); return RES(); } 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) { //lock first if possible if (ResourceCache::lock) { 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; if (ResourceCache::lock) { ResourceCache::lock->read_unlock(); } print_verbose("Loading resource: " + local_path + " (cached)"); return res; } } if (ResourceCache::lock) { ResourceCache::lock->read_unlock(); } } bool xl_remapped = false; String path = _path_remap(local_path, &xl_remapped); ERR_FAIL_COND_V(path == "", RES()); print_verbose("Loading resource: " + path); RES res = _load(path, local_path, p_type_hint, p_no_cache, r_error); if (res.is_null()) { 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 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 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 && ResourceCache::has(local_path)) { print_verbose("Loading resource: " + local_path + " (cached)"); Ref res_cached = ResourceCache::get(local_path); Ref ril = Ref(memnew(ResourceInteractiveLoaderDefault)); ril->resource = res_cached; return ril; } bool xl_remapped = false; String path = _path_remap(local_path, &xl_remapped); ERR_FAIL_COND_V(path == "", Ref()); 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 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); if (xl_remapped) ril->set_translation_remapped(true); return ril; } if (found) { ERR_EXPLAIN("Failed loading resource: " + path); } else { ERR_EXPLAIN("No loader found for resource: " + path); } ERR_FAIL_V(Ref()); return Ref(); } void ResourceLoader::add_resource_format_loader(ResourceFormatLoader *p_format_loader, bool p_at_front) { 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; } } 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; } 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 } void ResourceLoader::get_dependencies(const String &p_path, List *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 &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(new_path)) { Vector &v = *translation_remaps.getptr(new_path); String locale = TranslationServer::get_singleton()->get_locale(); if (r_translation_remapped) { *r_translation_remapped = true; } for (int i = 0; i < v.size(); i++) { int split = v[i].find_last(":"); if (split == -1) continue; String l = v[i].right(split + 1).strip_edges(); if (l == String()) continue; if (l.begins_with(locale)) { new_path = v[i].left(split); break; } } } if (path_remaps.has(new_path)) { new_path = path_remaps[new_path]; } if (new_path == p_path) { //did not remap //try file remap Error err; FileAccess *f = FileAccess::open(p_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, NULL, true); if (err == ERR_FILE_EOF) { break; } else if (err != OK) { ERR_PRINTS("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() { if (ResourceCache::lock) { ResourceCache::lock->read_lock(); } List to_reload; SelfList *E = remapped_list.first(); while (E) { to_reload.push_back(E->self()); E = E->next(); } if (ResourceCache::lock) { 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 keys; remaps.get_key_list(&keys); for (List::Element *E = keys.front(); E; E = E->next()) { Array langs = remaps[E->get()]; Vector 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(); } void ResourceLoader::load_path_remaps() { if (!ProjectSettings::get_singleton()->has_setting("path_remap/remapped_paths")) return; PoolVector remaps = ProjectSettings::get_singleton()->get("path_remap/remapped_paths"); int rc = remaps.size(); ERR_FAIL_COND(rc & 1); //must be even PoolVector::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(); } ResourceLoadErrorNotify ResourceLoader::err_notify = NULL; void *ResourceLoader::err_notify_ud = NULL; DependencyErrorNotify ResourceLoader::dep_err_notify = NULL; void *ResourceLoader::dep_err_notify_ud = NULL; bool ResourceLoader::abort_on_missing_resource = true; bool ResourceLoader::timestamp_on_load = false; SelfList::List ResourceLoader::remapped_list; HashMap > ResourceLoader::translation_remaps; HashMap ResourceLoader::path_remaps;