Remake resource thread safety and API

* Ensures thread safety when resources are destroyed.
* Simplified API by always forcing `ResourceCache::get_ref`, which needs less hacks and is fully thread safe.
* Removed RWLock for resources because its not possible to use for the new logic. Should not be a problem.

Supersedes #57533
This commit is contained in:
reduz 2022-06-22 13:46:46 +02:00
parent c18d0f2035
commit e772b65d92
15 changed files with 117 additions and 126 deletions

View file

@ -52,41 +52,36 @@ void Resource::set_path(const String &p_path, bool p_take_over) {
return;
}
if (p_path.is_empty()) {
p_take_over = false; // Can't take over an empty path
}
ResourceCache::lock.lock();
if (!path_cache.is_empty()) {
ResourceCache::lock.write_lock();
ResourceCache::resources.erase(path_cache);
ResourceCache::lock.write_unlock();
}
path_cache = "";
ResourceCache::lock.read_lock();
bool has_path = ResourceCache::resources.has(p_path);
ResourceCache::lock.read_unlock();
Ref<Resource> existing = ResourceCache::get_ref(p_path);
if (has_path) {
if (existing.is_valid()) {
if (p_take_over) {
ResourceCache::lock.write_lock();
Resource **res = ResourceCache::resources.getptr(p_path);
if (res) {
(*res)->set_name("");
}
ResourceCache::lock.write_unlock();
existing->path_cache = String();
ResourceCache::resources.erase(p_path);
} else {
ResourceCache::lock.read_lock();
bool exists = ResourceCache::resources.has(p_path);
ResourceCache::lock.read_unlock();
ERR_FAIL_COND_MSG(exists, "Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
ResourceCache::lock.unlock();
ERR_FAIL_MSG("Another resource is loaded from path '" + p_path + "' (possible cyclic resource inclusion).");
}
}
path_cache = p_path;
if (!path_cache.is_empty()) {
ResourceCache::lock.write_lock();
ResourceCache::resources[path_cache] = this;
ResourceCache::lock.write_unlock();
}
ResourceCache::lock.unlock();
_resource_path_changed();
}
@ -380,7 +375,7 @@ void Resource::set_as_translation_remapped(bool p_remapped) {
return;
}
ResourceCache::lock.write_lock();
ResourceCache::lock.lock();
if (p_remapped) {
ResourceLoader::remapped_list.add(&remapped_list);
@ -388,7 +383,7 @@ void Resource::set_as_translation_remapped(bool p_remapped) {
ResourceLoader::remapped_list.remove(&remapped_list);
}
ResourceCache::lock.write_unlock();
ResourceCache::lock.unlock();
}
bool Resource::is_translation_remapped() const {
@ -455,9 +450,9 @@ Resource::Resource() :
Resource::~Resource() {
if (!path_cache.is_empty()) {
ResourceCache::lock.write_lock();
ResourceCache::lock.lock();
ResourceCache::resources.erase(path_cache);
ResourceCache::lock.write_unlock();
ResourceCache::lock.unlock();
}
if (owners.size()) {
WARN_PRINT("Resource is still owned.");
@ -469,7 +464,7 @@ HashMap<String, Resource *> ResourceCache::resources;
HashMap<String, HashMap<String, String>> ResourceCache::resource_path_cache;
#endif
RWLock ResourceCache::lock;
Mutex ResourceCache::lock;
#ifdef TOOLS_ENABLED
RWLock ResourceCache::path_cache_lock;
#endif
@ -491,46 +486,67 @@ void ResourceCache::reload_externals() {
}
bool ResourceCache::has(const String &p_path) {
lock.read_lock();
bool b = resources.has(p_path);
lock.read_unlock();
return b;
}
Resource *ResourceCache::get(const String &p_path) {
lock.read_lock();
lock.lock();
Resource **res = resources.getptr(p_path);
lock.read_unlock();
if (!res) {
return nullptr;
if (res && (*res)->reference_get_count() == 0) {
// This resource is in the process of being deleted, ignore its existence.
(*res)->path_cache = String();
resources.erase(p_path);
res = nullptr;
}
return *res;
lock.unlock();
if (!res) {
return false;
}
return true;
}
Ref<Resource> ResourceCache::get_ref(const String &p_path) {
Ref<Resource> ref;
lock.lock();
Resource **res = resources.getptr(p_path);
if (res) {
ref = Ref<Resource>(*res);
}
if (res && !ref.is_valid()) {
// This resource is in the process of being deleted, ignore its existence
(*res)->path_cache = String();
resources.erase(p_path);
res = nullptr;
}
lock.unlock();
return ref;
}
void ResourceCache::get_cached_resources(List<Ref<Resource>> *p_resources) {
lock.read_lock();
lock.lock();
for (KeyValue<String, Resource *> &E : resources) {
p_resources->push_back(Ref<Resource>(E.value));
}
lock.read_unlock();
lock.unlock();
}
int ResourceCache::get_cached_resource_count() {
lock.read_lock();
lock.lock();
int rc = resources.size();
lock.read_unlock();
lock.unlock();
return rc;
}
void ResourceCache::dump(const char *p_file, bool p_short) {
#ifdef DEBUG_ENABLED
lock.read_lock();
lock.lock();
HashMap<String, int> type_count;
@ -562,7 +578,7 @@ void ResourceCache::dump(const char *p_file, bool p_short) {
}
}
lock.read_unlock();
lock.unlock();
#else
WARN_PRINT("ResourceCache::dump only with in debug builds.");
#endif

View file

@ -153,7 +153,7 @@ public:
class ResourceCache {
friend class Resource;
friend class ResourceLoader; //need the lock
static RWLock lock;
static Mutex lock;
static HashMap<String, Resource *> resources;
#ifdef TOOLS_ENABLED
static HashMap<String, HashMap<String, String>> resource_path_cache; // Each tscn has a set of resource paths and IDs.
@ -166,7 +166,7 @@ class ResourceCache {
public:
static void reload_externals();
static bool has(const String &p_path);
static Resource *get(const String &p_path);
static Ref<Resource> get_ref(const String &p_path);
static void dump(const char *p_file = nullptr, bool p_short = false);
static void get_cached_resources(List<Ref<Resource>> *p_resources);
static int get_cached_resource_count();

View file

@ -693,7 +693,7 @@ Error ResourceLoaderBinary::load() {
}
if (cache_mode == ResourceFormatLoader::CACHE_MODE_REUSE && ResourceCache::has(path)) {
Ref<Resource> cached = ResourceCache::get(path);
Ref<Resource> cached = ResourceCache::get_ref(path);
if (cached.is_valid()) {
//already loaded, don't do anything
error = OK;
@ -717,10 +717,10 @@ Error ResourceLoaderBinary::load() {
if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
//use the existing one
Resource *r = ResourceCache::get(path);
if (r->get_class() == t) {
r->reset_state();
res = Ref<Resource>(r);
Ref<Resource> cached = ResourceCache::get_ref(path);
if (cached->get_class() == t) {
cached->reset_state();
res = cached;
}
}

View file

@ -335,23 +335,15 @@ Error ResourceLoader::load_threaded_request(const String &p_path, const String &
thread_load_mutex->unlock();
ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Attempted to load a resource already being loaded from this thread, cyclic reference?");
}
//lock first if possible
ResourceCache::lock.read_lock();
//get ptr
Resource **rptr = ResourceCache::resources.getptr(local_path);
Ref<Resource> existing = ResourceCache::get_ref(local_path);
if (rptr) {
Ref<Resource> 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
load_task.resource = res;
load_task.status = THREAD_LOAD_LOADED;
load_task.progress = 1.0;
}
if (existing.is_valid()) {
//referencing is fine
load_task.resource = existing;
load_task.status = THREAD_LOAD_LOADED;
load_task.progress = 1.0;
}
ResourceCache::lock.read_unlock();
}
if (!p_source_resource.is_empty()) {
@ -530,27 +522,18 @@ Ref<Resource> ResourceLoader::load(const String &p_path, const String &p_type_hi
}
//Is it cached?
ResourceCache::lock.read_lock();
Resource **rptr = ResourceCache::resources.getptr(local_path);
Ref<Resource> existing = ResourceCache::get_ref(local_path);
if (rptr) {
Ref<Resource> res(*rptr);
if (existing.is_valid()) {
thread_load_mutex->unlock();
//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()) {
ResourceCache::lock.read_unlock();
thread_load_mutex->unlock();
if (r_error) {
*r_error = OK;
}
return res; //use cached
if (r_error) {
*r_error = OK;
}
}
ResourceCache::lock.read_unlock();
return existing; //use cached
}
//load using task (but this thread)
ThreadLoadTask load_task;
@ -867,7 +850,7 @@ String ResourceLoader::path_remap(const String &p_path) {
}
void ResourceLoader::reload_translation_remaps() {
ResourceCache::lock.read_lock();
ResourceCache::lock.lock();
List<Resource *> to_reload;
SelfList<Resource> *E = remapped_list.first();
@ -877,7 +860,7 @@ void ResourceLoader::reload_translation_remaps() {
E = E->next();
}
ResourceCache::lock.read_unlock();
ResourceCache::lock.unlock();
//now just make sure to not delete any of these resources while changing locale..
while (to_reload.front()) {

View file

@ -487,7 +487,7 @@ void DependencyRemoveDialog::show(const Vector<String> &p_folders, const Vector<
void DependencyRemoveDialog::ok_pressed() {
for (int i = 0; i < files_to_delete.size(); ++i) {
if (ResourceCache::has(files_to_delete[i])) {
Resource *res = ResourceCache::get(files_to_delete[i]);
Ref<Resource> res = ResourceCache::get_ref(files_to_delete[i]);
res->set_path("");
}

View file

@ -1792,9 +1792,9 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
//if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it
//to reload properly
if (ResourceCache::has(file)) {
Resource *r = ResourceCache::get(file);
Ref<Resource> r = ResourceCache::get_ref(file);
if (r.is_valid()) {
if (!r->get_import_path().is_empty()) {
String dst_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(file);
r->set_import_path(dst_path);
@ -2034,9 +2034,8 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
//if file is currently up, maybe the source it was loaded from changed, so import math must be updated for it
//to reload properly
if (ResourceCache::has(p_file)) {
Resource *r = ResourceCache::get(p_file);
Ref<Resource> r = ResourceCache::get_ref(p_file);
if (r.is_valid()) {
if (!r->get_import_path().is_empty()) {
String dst_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_file);
r->set_import_path(dst_path);

View file

@ -193,10 +193,7 @@ void EditorFolding::load_scene_folding(Node *p_scene, const String &p_path) {
for (int i = 0; i < res_unfolds.size(); i += 2) {
String path2 = res_unfolds[i];
Ref<Resource> res;
if (ResourceCache::has(path2)) {
res = Ref<Resource>(ResourceCache::get(path2));
}
Ref<Resource> res = ResourceCache::get_ref(path2);
if (res.is_null()) {
continue;
}

View file

@ -875,7 +875,7 @@ void EditorNode::_resources_changed(const Vector<String> &p_resources) {
int rc = p_resources.size();
for (int i = 0; i < rc; i++) {
Ref<Resource> res(ResourceCache::get(p_resources.get(i)));
Ref<Resource> res = ResourceCache::get_ref(p_resources.get(i));
if (res.is_null()) {
continue;
}
@ -1005,8 +1005,8 @@ void EditorNode::_resources_reimported(const Vector<String> &p_resources) {
continue;
}
// Reload normally.
Resource *resource = ResourceCache::get(p_resources[i]);
if (resource) {
Ref<Resource> resource = ResourceCache::get_ref(p_resources[i]);
if (resource.is_valid()) {
resource->reload_from_file();
}
}
@ -1719,7 +1719,7 @@ void EditorNode::_save_scene(String p_file, int idx) {
// We must update it, but also let the previous scene state go, as
// old version still work for referencing changes in instantiated or inherited scenes.
sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(ResourceCache::get(p_file)));
sdata = ResourceCache::get_ref(p_file);
if (sdata.is_valid()) {
sdata->recreate_state();
} else {
@ -3711,7 +3711,7 @@ Error EditorNode::load_scene(const String &p_scene, bool p_ignore_broken_deps, b
if (ResourceCache::has(lpath)) {
// Used from somewhere else? No problem! Update state and replace sdata.
Ref<PackedScene> ps = Ref<PackedScene>(Object::cast_to<PackedScene>(ResourceCache::get(lpath)));
Ref<PackedScene> ps = ResourceCache::get_ref(lpath);
if (ps.is_valid()) {
ps->replace_state(sdata->get_state());
ps->set_last_modified_time(sdata->get_last_modified_time());

View file

@ -1115,7 +1115,7 @@ Ref<Animation> ResourceImporterScene::_save_animation_to_file(Ref<Animation> ani
}
if (ResourceCache::has(p_save_to_path)) {
Ref<Animation> old_anim = Ref<Resource>(ResourceCache::get(p_save_to_path));
Ref<Animation> old_anim = ResourceCache::get_ref(p_save_to_path);
if (old_anim.is_valid()) {
old_anim->copy_from(anim);
anim = old_anim;
@ -1711,7 +1711,7 @@ void ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_m
}
if (!save_to_file.is_empty()) {
Ref<Mesh> existing = Ref<Resource>(ResourceCache::get(save_to_file));
Ref<Mesh> existing = ResourceCache::get_ref(save_to_file);
if (existing.is_valid()) {
//if somehow an existing one is useful, create
existing->reset_state();

View file

@ -306,10 +306,8 @@ Error ResourceImporterTextureAtlas::import_group_file(const String &p_group_file
//update cache if existing, else create
Ref<Texture2D> cache;
if (ResourceCache::has(p_group_file)) {
Resource *resptr = ResourceCache::get(p_group_file);
cache.reference_ptr(resptr);
} else {
cache = ResourceCache::get_ref(p_group_file);
if (!cache.is_valid()) {
Ref<ImageTexture> res_cache;
res_cache.instantiate();
res_cache->create_from_image(new_atlas);

View file

@ -5455,7 +5455,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String &
}
child->set_name(name);
Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(ResourceCache::get(path)));
Ref<Texture2D> texture = ResourceCache::get_ref(path);
if (parent) {
editor_data->get_undo_redo().add_do_method(parent, "add_child", child, true);

View file

@ -803,7 +803,7 @@ public:
//if the script is not in use by anyone, we can safely assume whatever we got is not casting to it.
return 1;
}
Ref<Script> cast_script = Ref<Resource>(ResourceCache::get(script));
Ref<Script> cast_script = ResourceCache::get_ref(script);
if (!cast_script.is_valid()) {
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
r_error_str = "Script path is not a script: " + script;

View file

@ -377,7 +377,7 @@ void VisualScriptFunctionCall::_update_method_cache() {
}
if (ResourceCache::has(base_script)) {
script = Ref<Resource>(ResourceCache::get(base_script));
script = ResourceCache::get_ref(base_script);
} else {
return;
}
@ -587,7 +587,7 @@ void VisualScriptFunctionCall::_validate_property(PropertyInfo &property) const
}
if (ResourceCache::has(base_script)) {
Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script));
Ref<Script> script = ResourceCache::get_ref(base_script);
if (script.is_valid()) {
property.hint = PROPERTY_HINT_METHOD_OF_SCRIPT;
property.hint_string = itos(script->get_instance_id());
@ -1178,7 +1178,7 @@ void VisualScriptPropertySet::_update_cache() {
}
if (ResourceCache::has(base_script)) {
script = Ref<Resource>(ResourceCache::get(base_script));
script = ResourceCache::get_ref(base_script);
} else {
return;
}
@ -1338,7 +1338,7 @@ void VisualScriptPropertySet::_validate_property(PropertyInfo &property) const {
}
if (ResourceCache::has(base_script)) {
Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script));
Ref<Script> script = ResourceCache::get_ref(base_script);
if (script.is_valid()) {
property.hint = PROPERTY_HINT_PROPERTY_OF_SCRIPT;
property.hint_string = itos(script->get_instance_id());
@ -1864,7 +1864,7 @@ void VisualScriptPropertyGet::_update_cache() {
}
if (ResourceCache::has(base_script)) {
script = Ref<Resource>(ResourceCache::get(base_script));
script = ResourceCache::get_ref(base_script);
} else {
return;
}
@ -2044,7 +2044,7 @@ void VisualScriptPropertyGet::_validate_property(PropertyInfo &property) const {
}
if (ResourceCache::has(base_script)) {
Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script));
Ref<Script> script = ResourceCache::get_ref(base_script);
if (script.is_valid()) {
property.hint = PROPERTY_HINT_PROPERTY_OF_SCRIPT;
property.hint_string = itos(script->get_instance_id());

View file

@ -701,7 +701,7 @@ void LiveEditor::_res_set_func(int p_id, const StringName &p_prop, const Variant
return;
}
Ref<Resource> r = ResourceCache::get(resp);
Ref<Resource> r = ResourceCache::get_ref(resp);
if (!r.is_valid()) {
return;
}
@ -728,7 +728,7 @@ void LiveEditor::_res_call_func(int p_id, const StringName &p_method, const Vari
return;
}
Ref<Resource> r = ResourceCache::get(resp);
Ref<Resource> r = ResourceCache::get_ref(resp);
if (!r.is_valid()) {
return;
}

View file

@ -528,9 +528,9 @@ Error ResourceLoaderText::load() {
if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(path)) {
//reuse existing
Resource *r = ResourceCache::get(path);
if (r && r->get_class() == type) {
res = Ref<Resource>(r);
Ref<Resource> cache = ResourceCache::get_ref(path);
if (cache.is_valid() && cache->get_class() == type) {
res = cache;
res->reset_state();
do_assign = true;
}
@ -539,10 +539,10 @@ Error ResourceLoaderText::load() {
MissingResource *missing_resource = nullptr;
if (res.is_null()) { //not reuse
if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && ResourceCache::has(path)) { //only if it doesn't exist
Ref<Resource> cache = ResourceCache::get_ref(path);
if (cache_mode != ResourceFormatLoader::CACHE_MODE_IGNORE && cache.is_valid()) { //only if it doesn't exist
//cached, do not assign
Resource *r = ResourceCache::get(path);
res = Ref<Resource>(r);
res = cache;
} else {
//create
@ -652,12 +652,10 @@ Error ResourceLoaderText::load() {
return error;
}
if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && ResourceCache::has(local_path)) {
Resource *r = ResourceCache::get(local_path);
if (r->get_class() == res_type) {
r->reset_state();
resource = Ref<Resource>(r);
}
Ref<Resource> cache = ResourceCache::get_ref(local_path);
if (cache_mode == ResourceFormatLoader::CACHE_MODE_REPLACE && cache.is_valid() && cache->get_class() == res_type) {
cache->reset_state();
resource = cache;
}
MissingResource *missing_resource = nullptr;