| 1 | /**************************************************************************/ | 
|---|
| 2 | /*  editor_resource_preview.cpp                                           */ | 
|---|
| 3 | /**************************************************************************/ | 
|---|
| 4 | /*                         This file is part of:                          */ | 
|---|
| 5 | /*                             GODOT ENGINE                               */ | 
|---|
| 6 | /*                        https://godotengine.org                         */ | 
|---|
| 7 | /**************************************************************************/ | 
|---|
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ | 
|---|
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */ | 
|---|
| 10 | /*                                                                        */ | 
|---|
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining  */ | 
|---|
| 12 | /* a copy of this software and associated documentation files (the        */ | 
|---|
| 13 | /* "Software"), to deal in the Software without restriction, including    */ | 
|---|
| 14 | /* without limitation the rights to use, copy, modify, merge, publish,    */ | 
|---|
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to     */ | 
|---|
| 16 | /* permit persons to whom the Software is furnished to do so, subject to  */ | 
|---|
| 17 | /* the following conditions:                                              */ | 
|---|
| 18 | /*                                                                        */ | 
|---|
| 19 | /* The above copyright notice and this permission notice shall be         */ | 
|---|
| 20 | /* included in all copies or substantial portions of the Software.        */ | 
|---|
| 21 | /*                                                                        */ | 
|---|
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */ | 
|---|
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */ | 
|---|
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ | 
|---|
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */ | 
|---|
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */ | 
|---|
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */ | 
|---|
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */ | 
|---|
| 29 | /**************************************************************************/ | 
|---|
| 30 |  | 
|---|
| 31 | #include "editor_resource_preview.h" | 
|---|
| 32 |  | 
|---|
| 33 | #include "core/config/project_settings.h" | 
|---|
| 34 | #include "core/io/file_access.h" | 
|---|
| 35 | #include "core/io/resource_loader.h" | 
|---|
| 36 | #include "core/io/resource_saver.h" | 
|---|
| 37 | #include "core/object/message_queue.h" | 
|---|
| 38 | #include "core/variant/variant_utility.h" | 
|---|
| 39 | #include "editor/editor_node.h" | 
|---|
| 40 | #include "editor/editor_paths.h" | 
|---|
| 41 | #include "editor/editor_scale.h" | 
|---|
| 42 | #include "editor/editor_settings.h" | 
|---|
| 43 | #include "editor/editor_string_names.h" | 
|---|
| 44 | #include "scene/resources/image_texture.h" | 
|---|
| 45 |  | 
|---|
| 46 | bool EditorResourcePreviewGenerator::handles(const String &p_type) const { | 
|---|
| 47 | bool success = false; | 
|---|
| 48 | if (GDVIRTUAL_CALL(_handles, p_type, success)) { | 
|---|
| 49 | return success; | 
|---|
| 50 | } | 
|---|
| 51 | ERR_FAIL_V_MSG(false, "EditorResourcePreviewGenerator::_handles needs to be overridden."); | 
|---|
| 52 | } | 
|---|
| 53 |  | 
|---|
| 54 | Ref<Texture2D> EditorResourcePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const { | 
|---|
| 55 | Ref<Texture2D> preview; | 
|---|
| 56 | if (GDVIRTUAL_CALL(_generate, p_from, p_size, p_metadata, preview)) { | 
|---|
| 57 | return preview; | 
|---|
| 58 | } | 
|---|
| 59 | ERR_FAIL_V_MSG(Ref<Texture2D>(), "EditorResourcePreviewGenerator::_generate needs to be overridden."); | 
|---|
| 60 | } | 
|---|
| 61 |  | 
|---|
| 62 | Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const { | 
|---|
| 63 | Ref<Texture2D> preview; | 
|---|
| 64 | if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, p_metadata, preview)) { | 
|---|
| 65 | return preview; | 
|---|
| 66 | } | 
|---|
| 67 |  | 
|---|
| 68 | Ref<Resource> res = ResourceLoader::load(p_path); | 
|---|
| 69 | if (!res.is_valid()) { | 
|---|
| 70 | return res; | 
|---|
| 71 | } | 
|---|
| 72 | return generate(res, p_size, p_metadata); | 
|---|
| 73 | } | 
|---|
| 74 |  | 
|---|
| 75 | bool EditorResourcePreviewGenerator::generate_small_preview_automatically() const { | 
|---|
| 76 | bool success = false; | 
|---|
| 77 | GDVIRTUAL_CALL(_generate_small_preview_automatically, success); | 
|---|
| 78 | return success; | 
|---|
| 79 | } | 
|---|
| 80 |  | 
|---|
| 81 | bool EditorResourcePreviewGenerator::can_generate_small_preview() const { | 
|---|
| 82 | bool success = false; | 
|---|
| 83 | GDVIRTUAL_CALL(_can_generate_small_preview, success); | 
|---|
| 84 | return success; | 
|---|
| 85 | } | 
|---|
| 86 |  | 
|---|
| 87 | void EditorResourcePreviewGenerator::_bind_methods() { | 
|---|
| 88 | GDVIRTUAL_BIND(_handles, "type"); | 
|---|
| 89 | GDVIRTUAL_BIND(_generate, "resource", "size", "metadata"); | 
|---|
| 90 | GDVIRTUAL_BIND(_generate_from_path, "path", "size", "metadata"); | 
|---|
| 91 | GDVIRTUAL_BIND(_generate_small_preview_automatically); | 
|---|
| 92 | GDVIRTUAL_BIND(_can_generate_small_preview); | 
|---|
| 93 | } | 
|---|
| 94 |  | 
|---|
| 95 | EditorResourcePreviewGenerator::EditorResourcePreviewGenerator() { | 
|---|
| 96 | } | 
|---|
| 97 |  | 
|---|
| 98 | EditorResourcePreview *EditorResourcePreview::singleton = nullptr; | 
|---|
| 99 |  | 
|---|
| 100 | void EditorResourcePreview::_thread_func(void *ud) { | 
|---|
| 101 | EditorResourcePreview *erp = (EditorResourcePreview *)ud; | 
|---|
| 102 | erp->_thread(); | 
|---|
| 103 | } | 
|---|
| 104 |  | 
|---|
| 105 | void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, ObjectID id, const StringName &p_func, const Variant &p_ud, const Dictionary &p_metadata) { | 
|---|
| 106 | { | 
|---|
| 107 | MutexLock lock(preview_mutex); | 
|---|
| 108 |  | 
|---|
| 109 | uint64_t modified_time = 0; | 
|---|
| 110 |  | 
|---|
| 111 | if (!p_path.begins_with( "ID:")) { | 
|---|
| 112 | modified_time = FileAccess::get_modified_time(p_path); | 
|---|
| 113 | } | 
|---|
| 114 |  | 
|---|
| 115 | Item item; | 
|---|
| 116 | item.order = order++; | 
|---|
| 117 | item.preview = p_texture; | 
|---|
| 118 | item.small_preview = p_small_texture; | 
|---|
| 119 | item.last_hash = p_hash; | 
|---|
| 120 | item.modified_time = modified_time; | 
|---|
| 121 | item.preview_metadata = p_metadata; | 
|---|
| 122 |  | 
|---|
| 123 | cache[p_path] = item; | 
|---|
| 124 | } | 
|---|
| 125 |  | 
|---|
| 126 | MessageQueue::get_singleton()->push_call(id, p_func, p_path, p_texture, p_small_texture, p_ud); | 
|---|
| 127 | } | 
|---|
| 128 |  | 
|---|
| 129 | void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata) { | 
|---|
| 130 | String type; | 
|---|
| 131 |  | 
|---|
| 132 | if (p_item.resource.is_valid()) { | 
|---|
| 133 | type = p_item.resource->get_class(); | 
|---|
| 134 | } else { | 
|---|
| 135 | type = ResourceLoader::get_resource_type(p_item.path); | 
|---|
| 136 | } | 
|---|
| 137 |  | 
|---|
| 138 | if (type.is_empty()) { | 
|---|
| 139 | r_texture = Ref<ImageTexture>(); | 
|---|
| 140 | r_small_texture = Ref<ImageTexture>(); | 
|---|
| 141 | return; //could not guess type | 
|---|
| 142 | } | 
|---|
| 143 |  | 
|---|
| 144 | int thumbnail_size = EDITOR_GET( "filesystem/file_dialog/thumbnail_size"); | 
|---|
| 145 | thumbnail_size *= EDSCALE; | 
|---|
| 146 |  | 
|---|
| 147 | r_texture = Ref<ImageTexture>(); | 
|---|
| 148 | r_small_texture = Ref<ImageTexture>(); | 
|---|
| 149 |  | 
|---|
| 150 | for (int i = 0; i < preview_generators.size(); i++) { | 
|---|
| 151 | if (!preview_generators[i]->handles(type)) { | 
|---|
| 152 | continue; | 
|---|
| 153 | } | 
|---|
| 154 |  | 
|---|
| 155 | Ref<Texture2D> generated; | 
|---|
| 156 | if (p_item.resource.is_valid()) { | 
|---|
| 157 | generated = preview_generators.write[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size), p_metadata); | 
|---|
| 158 | } else { | 
|---|
| 159 | generated = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size), p_metadata); | 
|---|
| 160 | } | 
|---|
| 161 | r_texture = generated; | 
|---|
| 162 |  | 
|---|
| 163 | if (preview_generators[i]->can_generate_small_preview()) { | 
|---|
| 164 | Ref<Texture2D> generated_small; | 
|---|
| 165 | Dictionary d; | 
|---|
| 166 | if (p_item.resource.is_valid()) { | 
|---|
| 167 | generated_small = preview_generators.write[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size), d); | 
|---|
| 168 | } else { | 
|---|
| 169 | generated_small = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size), d); | 
|---|
| 170 | } | 
|---|
| 171 | r_small_texture = generated_small; | 
|---|
| 172 | } | 
|---|
| 173 |  | 
|---|
| 174 | if (!r_small_texture.is_valid() && r_texture.is_valid() && preview_generators[i]->generate_small_preview_automatically()) { | 
|---|
| 175 | Ref<Image> small_image = r_texture->get_image(); | 
|---|
| 176 | small_image = small_image->duplicate(); | 
|---|
| 177 | small_image->resize(small_thumbnail_size, small_thumbnail_size, Image::INTERPOLATE_CUBIC); | 
|---|
| 178 | r_small_texture.instantiate(); | 
|---|
| 179 | r_small_texture->set_image(small_image); | 
|---|
| 180 | } | 
|---|
| 181 |  | 
|---|
| 182 | break; | 
|---|
| 183 | } | 
|---|
| 184 |  | 
|---|
| 185 | if (!p_item.resource.is_valid()) { | 
|---|
| 186 | // Cache the preview in case it's a resource on disk. | 
|---|
| 187 | if (r_texture.is_valid()) { | 
|---|
| 188 | // Wow it generated a preview... save cache. | 
|---|
| 189 | bool has_small_texture = r_small_texture.is_valid(); | 
|---|
| 190 | ResourceSaver::save(r_texture, cache_base + ".png"); | 
|---|
| 191 | if (has_small_texture) { | 
|---|
| 192 | ResourceSaver::save(r_small_texture, cache_base + "_small.png"); | 
|---|
| 193 | } | 
|---|
| 194 | Ref<FileAccess> f = FileAccess::open(cache_base + ".txt", FileAccess::WRITE); | 
|---|
| 195 | ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '"+ cache_base + ".txt'. Check user write permissions."); | 
|---|
| 196 | _write_preview_cache(f, thumbnail_size, has_small_texture, FileAccess::get_modified_time(p_item.path), FileAccess::get_md5(p_item.path), p_metadata); | 
|---|
| 197 | } | 
|---|
| 198 | } | 
|---|
| 199 | } | 
|---|
| 200 |  | 
|---|
| 201 | const Dictionary EditorResourcePreview::get_preview_metadata(const String &p_path) const { | 
|---|
| 202 | ERR_FAIL_COND_V(!cache.has(p_path), Dictionary()); | 
|---|
| 203 | return cache[p_path].preview_metadata; | 
|---|
| 204 | } | 
|---|
| 205 |  | 
|---|
| 206 | void EditorResourcePreview::_iterate() { | 
|---|
| 207 | preview_mutex.lock(); | 
|---|
| 208 |  | 
|---|
| 209 | if (queue.size()) { | 
|---|
| 210 | QueueItem item = queue.front()->get(); | 
|---|
| 211 | queue.pop_front(); | 
|---|
| 212 |  | 
|---|
| 213 | if (cache.has(item.path)) { | 
|---|
| 214 | // Already has it because someone loaded it, just let it know it's ready. | 
|---|
| 215 | _preview_ready(item.path, cache[item.path].last_hash, cache[item.path].preview, cache[item.path].small_preview, item.id, item.function, item.userdata, cache[item.path].preview_metadata); | 
|---|
| 216 |  | 
|---|
| 217 | preview_mutex.unlock(); | 
|---|
| 218 | } else { | 
|---|
| 219 | preview_mutex.unlock(); | 
|---|
| 220 |  | 
|---|
| 221 | Ref<ImageTexture> texture; | 
|---|
| 222 | Ref<ImageTexture> small_texture; | 
|---|
| 223 |  | 
|---|
| 224 | int thumbnail_size = EDITOR_GET( "filesystem/file_dialog/thumbnail_size"); | 
|---|
| 225 | thumbnail_size *= EDSCALE; | 
|---|
| 226 |  | 
|---|
| 227 | if (item.resource.is_valid()) { | 
|---|
| 228 | Dictionary preview_metadata; | 
|---|
| 229 | _generate_preview(texture, small_texture, item, String(), preview_metadata); | 
|---|
| 230 |  | 
|---|
| 231 | _preview_ready(item.path, item.resource->hash_edited_version(), texture, small_texture, item.id, item.function, item.userdata, preview_metadata); | 
|---|
| 232 |  | 
|---|
| 233 | } else { | 
|---|
| 234 | Dictionary preview_metadata; | 
|---|
| 235 | String temp_path = EditorPaths::get_singleton()->get_cache_dir(); | 
|---|
| 236 | String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text(); | 
|---|
| 237 | cache_base = temp_path.path_join( "resthumb-"+ cache_base); | 
|---|
| 238 |  | 
|---|
| 239 | // Does not have it, try to load a cached thumbnail. | 
|---|
| 240 |  | 
|---|
| 241 | String file = cache_base + ".txt"; | 
|---|
| 242 | Ref<FileAccess> f = FileAccess::open(file, FileAccess::READ); | 
|---|
| 243 | if (f.is_null()) { | 
|---|
| 244 | // No cache found, generate. | 
|---|
| 245 | _generate_preview(texture, small_texture, item, cache_base, preview_metadata); | 
|---|
| 246 | } else { | 
|---|
| 247 | uint64_t modtime = FileAccess::get_modified_time(item.path); | 
|---|
| 248 | int tsize; | 
|---|
| 249 | bool has_small_texture; | 
|---|
| 250 | uint64_t last_modtime; | 
|---|
| 251 | String hash; | 
|---|
| 252 | _read_preview_cache(f, &tsize, &has_small_texture, &last_modtime, &hash, &preview_metadata); | 
|---|
| 253 |  | 
|---|
| 254 | bool cache_valid = true; | 
|---|
| 255 |  | 
|---|
| 256 | if (tsize != thumbnail_size) { | 
|---|
| 257 | cache_valid = false; | 
|---|
| 258 | f.unref(); | 
|---|
| 259 | } else if (last_modtime != modtime) { | 
|---|
| 260 | String last_md5 = f->get_line(); | 
|---|
| 261 | String md5 = FileAccess::get_md5(item.path); | 
|---|
| 262 | f.unref(); | 
|---|
| 263 |  | 
|---|
| 264 | if (last_md5 != md5) { | 
|---|
| 265 | cache_valid = false; | 
|---|
| 266 | } else { | 
|---|
| 267 | // Update modified time. | 
|---|
| 268 |  | 
|---|
| 269 | Ref<FileAccess> f2 = FileAccess::open(file, FileAccess::WRITE); | 
|---|
| 270 | if (f2.is_null()) { | 
|---|
| 271 | // Not returning as this would leave the thread hanging and would require | 
|---|
| 272 | // some proper cleanup/disabling of resource preview generation. | 
|---|
| 273 | ERR_PRINT( "Cannot create file '"+ file + "'. Check user write permissions."); | 
|---|
| 274 | } else { | 
|---|
| 275 | _write_preview_cache(f2, thumbnail_size, has_small_texture, modtime, md5, preview_metadata); | 
|---|
| 276 | } | 
|---|
| 277 | } | 
|---|
| 278 | } else { | 
|---|
| 279 | f.unref(); | 
|---|
| 280 | } | 
|---|
| 281 |  | 
|---|
| 282 | if (cache_valid) { | 
|---|
| 283 | Ref<Image> img; | 
|---|
| 284 | img.instantiate(); | 
|---|
| 285 | Ref<Image> small_img; | 
|---|
| 286 | small_img.instantiate(); | 
|---|
| 287 |  | 
|---|
| 288 | if (img->load(cache_base + ".png") != OK) { | 
|---|
| 289 | cache_valid = false; | 
|---|
| 290 | } else { | 
|---|
| 291 | texture.instantiate(); | 
|---|
| 292 | texture->set_image(img); | 
|---|
| 293 |  | 
|---|
| 294 | if (has_small_texture) { | 
|---|
| 295 | if (small_img->load(cache_base + "_small.png") != OK) { | 
|---|
| 296 | cache_valid = false; | 
|---|
| 297 | } else { | 
|---|
| 298 | small_texture.instantiate(); | 
|---|
| 299 | small_texture->set_image(small_img); | 
|---|
| 300 | } | 
|---|
| 301 | } | 
|---|
| 302 | } | 
|---|
| 303 | } | 
|---|
| 304 |  | 
|---|
| 305 | if (!cache_valid) { | 
|---|
| 306 | _generate_preview(texture, small_texture, item, cache_base, preview_metadata); | 
|---|
| 307 | } | 
|---|
| 308 | } | 
|---|
| 309 | _preview_ready(item.path, 0, texture, small_texture, item.id, item.function, item.userdata, preview_metadata); | 
|---|
| 310 | } | 
|---|
| 311 | } | 
|---|
| 312 |  | 
|---|
| 313 | } else { | 
|---|
| 314 | preview_mutex.unlock(); | 
|---|
| 315 | } | 
|---|
| 316 | } | 
|---|
| 317 |  | 
|---|
| 318 | void EditorResourcePreview::_write_preview_cache(Ref<FileAccess> p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, String p_hash, const Dictionary &p_metadata) { | 
|---|
| 319 | p_file->store_line(itos(p_thumbnail_size)); | 
|---|
| 320 | p_file->store_line(itos(p_has_small_texture)); | 
|---|
| 321 | p_file->store_line(itos(p_modified_time)); | 
|---|
| 322 | p_file->store_line(p_hash); | 
|---|
| 323 | p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace( "\n", " ")); | 
|---|
| 324 | } | 
|---|
| 325 |  | 
|---|
| 326 | void EditorResourcePreview::_read_preview_cache(Ref<FileAccess> p_file, int *r_thumbnail_size, bool *r_has_small_texture, uint64_t *r_modified_time, String *r_hash, Dictionary *r_metadata) { | 
|---|
| 327 | *r_thumbnail_size = p_file->get_line().to_int(); | 
|---|
| 328 | *r_has_small_texture = p_file->get_line().to_int(); | 
|---|
| 329 | *r_modified_time = p_file->get_line().to_int(); | 
|---|
| 330 | *r_hash = p_file->get_line(); | 
|---|
| 331 | *r_metadata = VariantUtilityFunctions::str_to_var(p_file->get_line()); | 
|---|
| 332 | } | 
|---|
| 333 |  | 
|---|
| 334 | void EditorResourcePreview::_thread() { | 
|---|
| 335 | exited.clear(); | 
|---|
| 336 | while (!exit.is_set()) { | 
|---|
| 337 | preview_sem.wait(); | 
|---|
| 338 | _iterate(); | 
|---|
| 339 | } | 
|---|
| 340 | exited.set(); | 
|---|
| 341 | } | 
|---|
| 342 |  | 
|---|
| 343 | void EditorResourcePreview::_update_thumbnail_sizes() { | 
|---|
| 344 | if (small_thumbnail_size == -1) { | 
|---|
| 345 | // Kind of a workaround to retrieve the default icon size. | 
|---|
| 346 | small_thumbnail_size = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME( "Object"), EditorStringName(EditorIcons))->get_width(); | 
|---|
| 347 | } | 
|---|
| 348 | } | 
|---|
| 349 |  | 
|---|
| 350 | void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) { | 
|---|
| 351 | ERR_FAIL_NULL(p_receiver); | 
|---|
| 352 | ERR_FAIL_COND(!p_res.is_valid()); | 
|---|
| 353 | _update_thumbnail_sizes(); | 
|---|
| 354 |  | 
|---|
| 355 | { | 
|---|
| 356 | MutexLock lock(preview_mutex); | 
|---|
| 357 |  | 
|---|
| 358 | String path_id = "ID:"+ itos(p_res->get_instance_id()); | 
|---|
| 359 |  | 
|---|
| 360 | if (cache.has(path_id) && cache[path_id].last_hash == p_res->hash_edited_version()) { | 
|---|
| 361 | cache[path_id].order = order++; | 
|---|
| 362 | p_receiver->call(p_receiver_func, path_id, cache[path_id].preview, cache[path_id].small_preview, p_userdata); | 
|---|
| 363 | return; | 
|---|
| 364 | } | 
|---|
| 365 |  | 
|---|
| 366 | cache.erase(path_id); //erase if exists, since it will be regen | 
|---|
| 367 |  | 
|---|
| 368 | QueueItem item; | 
|---|
| 369 | item.function = p_receiver_func; | 
|---|
| 370 | item.id = p_receiver->get_instance_id(); | 
|---|
| 371 | item.resource = p_res; | 
|---|
| 372 | item.path = path_id; | 
|---|
| 373 | item.userdata = p_userdata; | 
|---|
| 374 |  | 
|---|
| 375 | queue.push_back(item); | 
|---|
| 376 | } | 
|---|
| 377 | preview_sem.post(); | 
|---|
| 378 | } | 
|---|
| 379 |  | 
|---|
| 380 | void EditorResourcePreview::queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) { | 
|---|
| 381 | _update_thumbnail_sizes(); | 
|---|
| 382 |  | 
|---|
| 383 | ERR_FAIL_NULL(p_receiver); | 
|---|
| 384 | { | 
|---|
| 385 | MutexLock lock(preview_mutex); | 
|---|
| 386 |  | 
|---|
| 387 | if (cache.has(p_path)) { | 
|---|
| 388 | cache[p_path].order = order++; | 
|---|
| 389 | p_receiver->call(p_receiver_func, p_path, cache[p_path].preview, cache[p_path].small_preview, p_userdata); | 
|---|
| 390 | return; | 
|---|
| 391 | } | 
|---|
| 392 |  | 
|---|
| 393 | QueueItem item; | 
|---|
| 394 | item.function = p_receiver_func; | 
|---|
| 395 | item.id = p_receiver->get_instance_id(); | 
|---|
| 396 | item.path = p_path; | 
|---|
| 397 | item.userdata = p_userdata; | 
|---|
| 398 |  | 
|---|
| 399 | queue.push_back(item); | 
|---|
| 400 | } | 
|---|
| 401 | preview_sem.post(); | 
|---|
| 402 | } | 
|---|
| 403 |  | 
|---|
| 404 | void EditorResourcePreview::add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator) { | 
|---|
| 405 | preview_generators.push_back(p_generator); | 
|---|
| 406 | } | 
|---|
| 407 |  | 
|---|
| 408 | void EditorResourcePreview::remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator) { | 
|---|
| 409 | preview_generators.erase(p_generator); | 
|---|
| 410 | } | 
|---|
| 411 |  | 
|---|
| 412 | EditorResourcePreview *EditorResourcePreview::get_singleton() { | 
|---|
| 413 | return singleton; | 
|---|
| 414 | } | 
|---|
| 415 |  | 
|---|
| 416 | void EditorResourcePreview::_bind_methods() { | 
|---|
| 417 | ClassDB::bind_method( "_preview_ready", &EditorResourcePreview::_preview_ready); | 
|---|
| 418 |  | 
|---|
| 419 | ClassDB::bind_method(D_METHOD( "queue_resource_preview", "path", "receiver", "receiver_func", "userdata"), &EditorResourcePreview::queue_resource_preview); | 
|---|
| 420 | ClassDB::bind_method(D_METHOD( "queue_edited_resource_preview", "resource", "receiver", "receiver_func", "userdata"), &EditorResourcePreview::queue_edited_resource_preview); | 
|---|
| 421 | ClassDB::bind_method(D_METHOD( "add_preview_generator", "generator"), &EditorResourcePreview::add_preview_generator); | 
|---|
| 422 | ClassDB::bind_method(D_METHOD( "remove_preview_generator", "generator"), &EditorResourcePreview::remove_preview_generator); | 
|---|
| 423 | ClassDB::bind_method(D_METHOD( "check_for_invalidation", "path"), &EditorResourcePreview::check_for_invalidation); | 
|---|
| 424 |  | 
|---|
| 425 | ADD_SIGNAL(MethodInfo( "preview_invalidated", PropertyInfo(Variant::STRING, "path"))); | 
|---|
| 426 | } | 
|---|
| 427 |  | 
|---|
| 428 | void EditorResourcePreview::check_for_invalidation(const String &p_path) { | 
|---|
| 429 | bool call_invalidated = false; | 
|---|
| 430 | { | 
|---|
| 431 | MutexLock lock(preview_mutex); | 
|---|
| 432 |  | 
|---|
| 433 | if (cache.has(p_path)) { | 
|---|
| 434 | uint64_t modified_time = FileAccess::get_modified_time(p_path); | 
|---|
| 435 | if (modified_time != cache[p_path].modified_time) { | 
|---|
| 436 | cache.erase(p_path); | 
|---|
| 437 | call_invalidated = true; | 
|---|
| 438 | } | 
|---|
| 439 | } | 
|---|
| 440 | } | 
|---|
| 441 |  | 
|---|
| 442 | if (call_invalidated) { //do outside mutex | 
|---|
| 443 | call_deferred(SNAME( "emit_signal"), "preview_invalidated", p_path); | 
|---|
| 444 | } | 
|---|
| 445 | } | 
|---|
| 446 |  | 
|---|
| 447 | void EditorResourcePreview::start() { | 
|---|
| 448 | if (DisplayServer::get_singleton()->get_name() != "headless") { | 
|---|
| 449 | ERR_FAIL_COND_MSG(thread.is_started(), "Thread already started."); | 
|---|
| 450 | thread.start(_thread_func, this); | 
|---|
| 451 | } | 
|---|
| 452 | } | 
|---|
| 453 |  | 
|---|
| 454 | void EditorResourcePreview::stop() { | 
|---|
| 455 | if (thread.is_started()) { | 
|---|
| 456 | exit.set(); | 
|---|
| 457 | preview_sem.post(); | 
|---|
| 458 | while (!exited.is_set()) { | 
|---|
| 459 | OS::get_singleton()->delay_usec(10000); | 
|---|
| 460 | RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on rendering server | 
|---|
| 461 | } | 
|---|
| 462 | thread.wait_to_finish(); | 
|---|
| 463 | } | 
|---|
| 464 | } | 
|---|
| 465 |  | 
|---|
| 466 | EditorResourcePreview::EditorResourcePreview() { | 
|---|
| 467 | singleton = this; | 
|---|
| 468 | order = 0; | 
|---|
| 469 | } | 
|---|
| 470 |  | 
|---|
| 471 | EditorResourcePreview::~EditorResourcePreview() { | 
|---|
| 472 | stop(); | 
|---|
| 473 | } | 
|---|
| 474 |  | 
|---|