Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/inspector/editor_resource_preview.cpp
20901 views
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/variant/variant_utility.h"
38
#include "editor/editor_node.h"
39
#include "editor/editor_string_names.h"
40
#include "editor/file_system/editor_paths.h"
41
#include "editor/settings/editor_settings.h"
42
#include "editor/themes/editor_scale.h"
43
#include "scene/main/window.h"
44
#include "scene/resources/image_texture.h"
45
#include "servers/rendering/rendering_server_globals.h"
46
47
bool EditorResourcePreviewGenerator::handles(const String &p_type) const {
48
bool success = false;
49
GDVIRTUAL_CALL(_handles, p_type, success);
50
return success;
51
}
52
53
Ref<Texture2D> EditorResourcePreviewGenerator::generate(const Ref<Resource> &p_from, const Size2 &p_size, Dictionary &p_metadata) const {
54
Ref<Texture2D> preview;
55
GDVIRTUAL_CALL(_generate, p_from, p_size, p_metadata, preview);
56
return preview;
57
}
58
59
Ref<Texture2D> EditorResourcePreviewGenerator::generate_from_path(const String &p_path, const Size2 &p_size, Dictionary &p_metadata) const {
60
Ref<Texture2D> preview;
61
if (GDVIRTUAL_CALL(_generate_from_path, p_path, p_size, p_metadata, preview)) {
62
return preview;
63
}
64
65
Ref<Resource> res = ResourceLoader::load(p_path);
66
if (res.is_null()) {
67
return res;
68
}
69
return generate(res, p_size, p_metadata);
70
}
71
72
bool EditorResourcePreviewGenerator::generate_small_preview_automatically() const {
73
bool success = false;
74
GDVIRTUAL_CALL(_generate_small_preview_automatically, success);
75
return success;
76
}
77
78
bool EditorResourcePreviewGenerator::can_generate_small_preview() const {
79
bool success = false;
80
GDVIRTUAL_CALL(_can_generate_small_preview, success);
81
return success;
82
}
83
84
void EditorResourcePreviewGenerator::_bind_methods() {
85
GDVIRTUAL_BIND(_handles, "type");
86
GDVIRTUAL_BIND(_generate, "resource", "size", "metadata");
87
GDVIRTUAL_BIND(_generate_from_path, "path", "size", "metadata");
88
GDVIRTUAL_BIND(_generate_small_preview_automatically);
89
GDVIRTUAL_BIND(_can_generate_small_preview);
90
91
ClassDB::bind_method(D_METHOD("request_draw_and_wait", "viewport"), &EditorResourcePreviewGenerator::request_draw_and_wait);
92
}
93
94
void EditorResourcePreviewGenerator::DrawRequester::request_and_wait(RID p_viewport) {
95
if (EditorResourcePreview::get_singleton()->is_threaded()) {
96
RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(this, &EditorResourcePreviewGenerator::DrawRequester::_prepare_draw).bind(p_viewport), Object::CONNECT_ONE_SHOT);
97
semaphore.wait();
98
} else {
99
// Avoid the main viewport and children being redrawn.
100
SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
101
ERR_FAIL_NULL_MSG(st, "Editor's MainLoop is not a SceneTree. This is a bug.");
102
RID root_vp = st->get_root()->get_viewport_rid();
103
RenderingServer::get_singleton()->viewport_set_active(root_vp, false);
104
105
RS::get_singleton()->viewport_set_update_mode(p_viewport, RS::VIEWPORT_UPDATE_ONCE);
106
RS::get_singleton()->draw(false);
107
108
// Let main viewport and children be drawn again.
109
RenderingServer::get_singleton()->viewport_set_active(root_vp, true);
110
}
111
}
112
113
void EditorResourcePreviewGenerator::DrawRequester::abort() {
114
if (EditorResourcePreview::get_singleton()->is_threaded()) {
115
semaphore.post();
116
}
117
}
118
119
void EditorResourcePreviewGenerator::request_draw_and_wait(RID viewport) const {
120
DrawRequester draw_requester;
121
draw_requester.request_and_wait(viewport);
122
}
123
124
void EditorResourcePreviewGenerator::DrawRequester::_prepare_draw(RID p_viewport) {
125
RS::get_singleton()->viewport_set_update_mode(p_viewport, RS::VIEWPORT_UPDATE_ONCE);
126
RS::get_singleton()->request_frame_drawn_callback(callable_mp(this, &EditorResourcePreviewGenerator::DrawRequester::_post_semaphore));
127
}
128
129
void EditorResourcePreviewGenerator::DrawRequester::_post_semaphore() {
130
semaphore.post();
131
}
132
133
bool EditorResourcePreview::is_threaded() const {
134
return RSG::rasterizer->can_create_resources_async();
135
}
136
137
void EditorResourcePreview::_thread_func(void *ud) {
138
EditorResourcePreview *erp = (EditorResourcePreview *)ud;
139
erp->_thread();
140
}
141
142
void EditorResourcePreview::_preview_ready(const String &p_path, int p_hash, const Ref<Texture2D> &p_texture, const Ref<Texture2D> &p_small_texture, const Callable &p_callback, const Dictionary &p_metadata) {
143
{
144
MutexLock lock(preview_mutex);
145
146
uint64_t modified_time = 0;
147
148
if (!p_path.begins_with("ID:")) {
149
modified_time = FileAccess::get_modified_time(p_path);
150
String import_path = p_path + ".import";
151
if (FileAccess::exists(import_path)) {
152
modified_time = MAX(modified_time, FileAccess::get_modified_time(import_path));
153
}
154
}
155
156
Item item;
157
item.preview = p_texture;
158
item.small_preview = p_small_texture;
159
item.last_hash = p_hash;
160
item.modified_time = modified_time;
161
item.preview_metadata = p_metadata;
162
163
cache[p_path] = item;
164
}
165
p_callback.call_deferred(p_path, p_texture, p_small_texture);
166
}
167
168
void EditorResourcePreview::_generate_preview(Ref<ImageTexture> &r_texture, Ref<ImageTexture> &r_small_texture, const QueueItem &p_item, const String &cache_base, Dictionary &p_metadata) {
169
String type;
170
171
uint64_t started_at = OS::get_singleton()->get_ticks_usec();
172
173
if (p_item.resource.is_valid()) {
174
type = p_item.resource->get_class();
175
} else {
176
type = ResourceLoader::get_resource_type(p_item.path);
177
}
178
179
if (type.is_empty()) {
180
r_texture = Ref<ImageTexture>();
181
r_small_texture = Ref<ImageTexture>();
182
183
if (is_print_verbose_enabled()) {
184
print_line(vformat("Generated '%s' preview in %d usec", p_item.path, OS::get_singleton()->get_ticks_usec() - started_at));
185
}
186
return; //could not guess type
187
}
188
189
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
190
thumbnail_size *= EDSCALE;
191
192
r_texture = Ref<ImageTexture>();
193
r_small_texture = Ref<ImageTexture>();
194
195
for (int i = 0; i < preview_generators.size(); i++) {
196
if (!preview_generators[i]->handles(type)) {
197
continue;
198
}
199
200
Ref<Texture2D> generated;
201
if (p_item.resource.is_valid()) {
202
generated = preview_generators.write[i]->generate(p_item.resource, Vector2(thumbnail_size, thumbnail_size), p_metadata);
203
} else {
204
generated = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(thumbnail_size, thumbnail_size), p_metadata);
205
}
206
r_texture = generated;
207
208
if (preview_generators[i]->can_generate_small_preview()) {
209
Ref<Texture2D> generated_small;
210
Dictionary d;
211
if (p_item.resource.is_valid()) {
212
generated_small = preview_generators.write[i]->generate(p_item.resource, Vector2(small_thumbnail_size, small_thumbnail_size), d);
213
} else {
214
generated_small = preview_generators.write[i]->generate_from_path(p_item.path, Vector2(small_thumbnail_size, small_thumbnail_size), d);
215
}
216
r_small_texture = generated_small;
217
}
218
219
if (r_small_texture.is_null() && r_texture.is_valid() && preview_generators[i]->generate_small_preview_automatically()) {
220
Ref<Image> small_image = r_texture->get_image()->duplicate();
221
Vector2i new_size = Vector2i(1, 1) * small_thumbnail_size;
222
const real_t aspect = small_image->get_size().aspect();
223
if (aspect > 1.0) {
224
new_size.y = MAX(1, new_size.y / aspect);
225
} else if (aspect < 1.0) {
226
new_size.x = MAX(1, new_size.x * aspect);
227
}
228
small_image->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC);
229
230
// Make sure the image is always square.
231
if (aspect != 1.0) {
232
Ref<Image> rect = small_image;
233
const Vector2i rect_size = rect->get_size();
234
small_image = Image::create_empty(small_thumbnail_size, small_thumbnail_size, false, rect->get_format());
235
// Blit the rectangle in the center of the square.
236
small_image->blit_rect(rect, Rect2i(Vector2i(), rect_size), (Vector2i(1, 1) * small_thumbnail_size - rect_size) / 2);
237
}
238
239
r_small_texture.instantiate();
240
r_small_texture->set_image(small_image);
241
}
242
243
if (generated.is_valid()) {
244
break;
245
}
246
}
247
248
if (p_item.resource.is_null()) {
249
// Cache the preview in case it's a resource on disk.
250
if (r_texture.is_valid()) {
251
// Wow it generated a preview... save cache.
252
bool has_small_texture = r_small_texture.is_valid();
253
ResourceSaver::save(r_texture, cache_base + ".png");
254
if (has_small_texture) {
255
ResourceSaver::save(r_small_texture, cache_base + "_small.png");
256
}
257
Ref<FileAccess> f = FileAccess::open(cache_base + ".txt", FileAccess::WRITE);
258
ERR_FAIL_COND_MSG(f.is_null(), "Cannot create file '" + cache_base + ".txt'. Check user write permissions.");
259
260
uint64_t modtime = FileAccess::get_modified_time(p_item.path);
261
String import_path = p_item.path + ".import";
262
if (FileAccess::exists(import_path)) {
263
modtime = MAX(modtime, FileAccess::get_modified_time(import_path));
264
}
265
266
_write_preview_cache(f, thumbnail_size, has_small_texture, modtime, FileAccess::get_md5(p_item.path), p_metadata);
267
}
268
}
269
270
if (is_print_verbose_enabled()) {
271
print_line(vformat("Generated '%s' preview in %d usec", p_item.path, OS::get_singleton()->get_ticks_usec() - started_at));
272
}
273
}
274
275
const Dictionary EditorResourcePreview::get_preview_metadata(const String &p_path) const {
276
ERR_FAIL_COND_V(!cache.has(p_path), Dictionary());
277
return cache[p_path].preview_metadata;
278
}
279
280
void EditorResourcePreview::_iterate() {
281
preview_mutex.lock();
282
283
if (queue.is_empty()) {
284
preview_mutex.unlock();
285
return;
286
}
287
288
QueueItem item = queue.front()->get();
289
queue.pop_front();
290
291
if (cache.has(item.path)) {
292
Item cached_item = cache[item.path];
293
// Already has it because someone loaded it, just let it know it's ready.
294
_preview_ready(item.path, cached_item.last_hash, cached_item.preview, cached_item.small_preview, item.callback, cached_item.preview_metadata);
295
preview_mutex.unlock();
296
return;
297
}
298
preview_mutex.unlock();
299
300
Ref<ImageTexture> texture;
301
Ref<ImageTexture> small_texture;
302
303
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
304
thumbnail_size *= EDSCALE;
305
306
if (item.resource.is_valid()) {
307
Dictionary preview_metadata;
308
_generate_preview(texture, small_texture, item, String(), preview_metadata);
309
_preview_ready(item.path, item.resource->hash_edited_version_for_preview(), texture, small_texture, item.callback, preview_metadata);
310
return;
311
}
312
313
Dictionary preview_metadata;
314
String temp_path = EditorPaths::get_singleton()->get_cache_dir();
315
String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text();
316
cache_base = temp_path.path_join("resthumb-" + cache_base);
317
318
// Does not have it, try to load a cached thumbnail.
319
320
String file = cache_base + ".txt";
321
Ref<FileAccess> f = FileAccess::open(file, FileAccess::READ);
322
if (f.is_null()) {
323
// No cache found, generate.
324
_generate_preview(texture, small_texture, item, cache_base, preview_metadata);
325
} else {
326
uint64_t modtime = FileAccess::get_modified_time(item.path);
327
String import_path = item.path + ".import";
328
if (FileAccess::exists(import_path)) {
329
modtime = MAX(modtime, FileAccess::get_modified_time(import_path));
330
}
331
332
int tsize;
333
bool has_small_texture;
334
uint64_t last_modtime;
335
String hash;
336
bool outdated;
337
_read_preview_cache(f, &tsize, &has_small_texture, &last_modtime, &hash, &preview_metadata, &outdated);
338
339
bool cache_valid = true;
340
341
if (tsize != thumbnail_size) {
342
cache_valid = false;
343
f.unref();
344
} else if (outdated) {
345
cache_valid = false;
346
f.unref();
347
} else if (last_modtime != modtime) {
348
String last_md5 = f->get_line();
349
String md5 = FileAccess::get_md5(item.path);
350
f.unref();
351
352
if (last_md5 != md5) {
353
cache_valid = false;
354
} else {
355
// Update modified time.
356
357
Ref<FileAccess> f2 = FileAccess::open(file, FileAccess::WRITE);
358
if (f2.is_null()) {
359
// Not returning as this would leave the thread hanging and would require
360
// some proper cleanup/disabling of resource preview generation.
361
ERR_PRINT("Cannot create file '" + file + "'. Check user write permissions.");
362
} else {
363
_write_preview_cache(f2, thumbnail_size, has_small_texture, modtime, md5, preview_metadata);
364
}
365
}
366
} else {
367
f.unref();
368
}
369
370
if (cache_valid) {
371
Ref<Image> img;
372
img.instantiate();
373
Ref<Image> small_img;
374
small_img.instantiate();
375
376
if (img->load(cache_base + ".png") != OK) {
377
cache_valid = false;
378
} else {
379
texture.instantiate();
380
texture->set_image(img);
381
382
if (has_small_texture) {
383
if (small_img->load(cache_base + "_small.png") != OK) {
384
cache_valid = false;
385
} else {
386
small_texture.instantiate();
387
small_texture->set_image(small_img);
388
}
389
}
390
}
391
}
392
393
if (!cache_valid) {
394
_generate_preview(texture, small_texture, item, cache_base, preview_metadata);
395
}
396
}
397
_preview_ready(item.path, 0, texture, small_texture, item.callback, preview_metadata);
398
}
399
400
void EditorResourcePreview::_write_preview_cache(Ref<FileAccess> p_file, int p_thumbnail_size, bool p_has_small_texture, uint64_t p_modified_time, const String &p_hash, const Dictionary &p_metadata) {
401
p_file->store_line(itos(p_thumbnail_size));
402
p_file->store_line(itos(p_has_small_texture));
403
p_file->store_line(itos(p_modified_time));
404
p_file->store_line(p_hash);
405
p_file->store_line(VariantUtilityFunctions::var_to_str(p_metadata).replace_char('\n', ' '));
406
p_file->store_line(itos(CURRENT_METADATA_VERSION));
407
}
408
409
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, bool *r_outdated) {
410
*r_thumbnail_size = p_file->get_line().to_int();
411
*r_has_small_texture = p_file->get_line().to_int();
412
*r_modified_time = p_file->get_line().to_int();
413
*r_hash = p_file->get_line();
414
*r_metadata = VariantUtilityFunctions::str_to_var(p_file->get_line());
415
*r_outdated = p_file->get_line().to_int() < CURRENT_METADATA_VERSION;
416
}
417
418
void EditorResourcePreview::_thread() {
419
exited.clear();
420
while (!exiting.is_set()) {
421
preview_sem.wait();
422
_iterate();
423
}
424
exited.set();
425
}
426
427
void EditorResourcePreview::_idle_callback() {
428
if (!singleton) {
429
// Just in case the shutdown of the editor involves the deletion of the singleton
430
// happening while additional idle callbacks can happen.
431
return;
432
}
433
434
// Process preview tasks, trying to leave a little bit of responsiveness worst case.
435
uint64_t start = OS::get_singleton()->get_ticks_msec();
436
while (!singleton->queue.is_empty() && OS::get_singleton()->get_ticks_msec() - start < 100) {
437
singleton->_iterate();
438
}
439
}
440
441
void EditorResourcePreview::_update_thumbnail_sizes() {
442
if (small_thumbnail_size == -1) {
443
// Kind of a workaround to retrieve the default icon size.
444
small_thumbnail_size = EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("Object"), EditorStringName(EditorIcons))->get_width();
445
}
446
}
447
448
EditorResourcePreview::PreviewItem EditorResourcePreview::get_resource_preview_if_available(const String &p_path) {
449
PreviewItem item;
450
{
451
MutexLock lock(preview_mutex);
452
453
HashMap<String, EditorResourcePreview::Item>::Iterator I = cache.find(p_path);
454
if (!I) {
455
return item;
456
}
457
458
EditorResourcePreview::Item &cached_item = I->value;
459
item.preview = cached_item.preview;
460
item.small_preview = cached_item.small_preview;
461
}
462
preview_sem.post();
463
return item;
464
}
465
466
void EditorResourcePreview::_queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) {
467
ERR_FAIL_NULL(p_receiver);
468
queue_edited_resource_preview(p_res, Callable(p_receiver, p_receiver_func).bind(p_userdata));
469
}
470
471
void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p_res, const Callable &p_callback) {
472
ERR_FAIL_COND(p_res.is_null());
473
_update_thumbnail_sizes();
474
475
{
476
MutexLock lock(preview_mutex);
477
478
String path_id = "ID:" + itos(p_res->get_instance_id());
479
HashMap<String, EditorResourcePreview::Item>::Iterator I = cache.find(path_id);
480
481
if (I && I->value.last_hash == p_res->hash_edited_version_for_preview()) {
482
p_callback.call(path_id, I->value.preview, I->value.small_preview);
483
return;
484
}
485
486
if (I) {
487
cache.remove(I); // Erase if exists, since it will be regen.
488
}
489
490
QueueItem item;
491
item.resource = p_res;
492
item.path = path_id;
493
item.callback = p_callback;
494
queue.push_back(item);
495
}
496
preview_sem.post();
497
}
498
499
void EditorResourcePreview::_queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) {
500
ERR_FAIL_NULL(p_receiver);
501
queue_resource_preview(p_path, Callable(p_receiver, p_receiver_func).bind(p_userdata));
502
}
503
504
void EditorResourcePreview::queue_resource_preview(const String &p_path, const Callable &p_callback) {
505
_update_thumbnail_sizes();
506
507
{
508
MutexLock lock(preview_mutex);
509
510
const Item *cached_item = cache.getptr(p_path);
511
if (cached_item) {
512
p_callback.call(p_path, cached_item->preview, cached_item->small_preview);
513
return;
514
}
515
516
QueueItem item;
517
item.path = p_path;
518
item.callback = p_callback;
519
queue.push_back(item);
520
}
521
preview_sem.post();
522
}
523
524
void EditorResourcePreview::add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator) {
525
preview_generators.push_back(p_generator);
526
}
527
528
void EditorResourcePreview::remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator) {
529
preview_generators.erase(p_generator);
530
}
531
532
EditorResourcePreview *EditorResourcePreview::get_singleton() {
533
return singleton;
534
}
535
536
void EditorResourcePreview::_bind_methods() {
537
ClassDB::bind_method(D_METHOD("queue_resource_preview", "path", "receiver", "receiver_func", "userdata"), &EditorResourcePreview::_queue_resource_preview);
538
ClassDB::bind_method(D_METHOD("queue_edited_resource_preview", "resource", "receiver", "receiver_func", "userdata"), &EditorResourcePreview::_queue_edited_resource_preview);
539
ClassDB::bind_method(D_METHOD("add_preview_generator", "generator"), &EditorResourcePreview::add_preview_generator);
540
ClassDB::bind_method(D_METHOD("remove_preview_generator", "generator"), &EditorResourcePreview::remove_preview_generator);
541
ClassDB::bind_method(D_METHOD("check_for_invalidation", "path"), &EditorResourcePreview::check_for_invalidation);
542
543
ADD_SIGNAL(MethodInfo("preview_invalidated", PropertyInfo(Variant::STRING, "path")));
544
}
545
546
void EditorResourcePreview::_notification(int p_what) {
547
switch (p_what) {
548
case NOTIFICATION_EXIT_TREE: {
549
stop();
550
} break;
551
}
552
}
553
554
void EditorResourcePreview::check_for_invalidation(const String &p_path) {
555
bool call_invalidated = false;
556
{
557
MutexLock lock(preview_mutex);
558
559
if (cache.has(p_path)) {
560
uint64_t modified_time = FileAccess::get_modified_time(p_path);
561
String import_path = p_path + ".import";
562
if (FileAccess::exists(import_path)) {
563
modified_time = MAX(modified_time, FileAccess::get_modified_time(import_path));
564
}
565
566
if (modified_time != cache[p_path].modified_time) {
567
cache.erase(p_path);
568
call_invalidated = true;
569
}
570
}
571
}
572
573
if (call_invalidated) { //do outside mutex
574
call_deferred(SNAME("emit_signal"), "preview_invalidated", p_path);
575
}
576
}
577
578
void EditorResourcePreview::start() {
579
if (DisplayServer::get_singleton()->get_name() == "headless") {
580
return;
581
}
582
583
if (is_threaded()) {
584
ERR_FAIL_COND_MSG(thread.is_started(), "Thread already started.");
585
thread.start(_thread_func, this);
586
} else {
587
SceneTree *st = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
588
ERR_FAIL_NULL_MSG(st, "Editor's MainLoop is not a SceneTree. This is a bug.");
589
st->add_idle_callback(&_idle_callback);
590
}
591
}
592
593
void EditorResourcePreview::stop() {
594
if (is_threaded()) {
595
if (thread.is_started()) {
596
exiting.set();
597
preview_sem.post();
598
599
for (int i = 0; i < preview_generators.size(); i++) {
600
preview_generators.write[i]->abort();
601
}
602
603
while (!exited.is_set()) {
604
// Sync pending work.
605
OS::get_singleton()->delay_usec(10000);
606
RenderingServer::get_singleton()->sync();
607
MessageQueue::get_singleton()->flush();
608
}
609
610
thread.wait_to_finish();
611
}
612
}
613
}
614
615
EditorResourcePreview::EditorResourcePreview() {
616
singleton = this;
617
}
618
619
EditorResourcePreview::~EditorResourcePreview() {
620
stop();
621
}
622
623