Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
supervisor.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel.
3 Copyright (C) 2026 Aurélien Pierre.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#include "develop/supervisor.h"
20#include "common/image.h" // dt_image_t
21#include "common/introspection.h" // dt_introspection_field_t
22#include "develop/imageop.h" // dt_iop_module_t (introspection accessors)
23#include "develop/blend.h" // dt_develop_blend_params_t + name tables
24#include "develop/masks.h" // dt_masks_form_t + group members
25#include "develop/pixelpipe_cache.h" // DT_PIXELPIPE_CACHE_HASH_INVALID
26#include "develop/pixelpipe_hb.h" // dt_pixelpipe_get_pipe_name
27
28#include <json-glib/json-glib.h>
29#include <stdarg.h>
30#include <stdio.h>
31
32// Which facets registered against a given hash. A node-output hash, its
33// cacheline and the backbuffer that promotes it all share one key, so several
34// facets coexist on the same entry.
48
49// One registry entry, identified by a single hash. All fields are value-copied;
50// the registry never points at a live foreign object. Entries are never removed
51// (registry as memory); `alive` tracks whether the represented object still
52// exists in the application.
53typedef struct dt_sv_entry_t
54{
56 guint facets; // bitmask of dt_sv_facet_t
57 gboolean alive; // FALSE after the represented object was deleted/evicted
58
59 // linkage edges (INVALID means "unset")
60 uint64_t param_hash; // node/cacheline -> history/module parameter identity
61 uint64_t input_hash; // cacheline -> the input cacheline it consumes
62 uint64_t node_hash; // cacheline -> producing topology node
63 uint64_t history_hash; // backbuf -> history-stack state
64 uint64_t image_hash; // mipmap -> image cache object
65 uint64_t pred_hash; // node -> predecessor node in the pipeline
66
67 // descriptive metadata (copied in)
68 char op_name[32];
69 char multi_name[64];
74 int32_t imgid;
76 int devid;
77 gboolean enabled;
78
79 // cacheline facet
80 size_t size;
82 char cl_name[64];
83
84 // backbuffer facet
86
87 // widget facet
88 char widget_tag[48];
89
90 // thumbnail facet
91 int mip;
92 gboolean thumb_success;
93
94 // mipmap facet
95 char img_filename[128];
96
97 // form facet
98 int formid;
100 char form_name[64];
101
102 // cache-wait facet
103 uint64_t awaits_hash; // cache-wait -> the cacheline hash the GUI is blocked on
104 uint64_t request_id; // numeric identity of the wait request
105 char owner_tag[48]; // consumer label (e.g. "color-picker-input")
106 gboolean request_emitted; // TRUE when this event also (re)emitted a CACHE_REQUEST
108
109// Cap on the retained GUI event log. ~200 B/event keeps this a few MB.
110#define DT_SV_LOG_MAX 20000
111
113
114static struct
115{
116 GHashTable *entries; // uint64_t hash -> dt_sv_entry_t*
117 GQueue *log; // of dt_sv_logged_event_t*, oldest at head
118 uint64_t next_seq; // monotonic capture sequence
119 dt_pthread_mutex_t lock;
120 gboolean inited;
121} _sv = { 0 };
122
123static void _logged_event_free(gpointer p)
124{
126 if(!e) return;
127 if(e->links) g_array_free(e->links, TRUE);
128 g_free(e->json);
129 g_free(e);
130}
131
132static inline gboolean _hash_is_set(const uint64_t h)
133{
134 return h != 0 && h != DT_PIXELPIPE_CACHE_HASH_INVALID;
135}
136
137static const char *_op_str(const dt_sv_op_t op)
138{
139 switch(op)
140 {
141 case DT_SV_CREATE: return "create";
142 case DT_SV_UPDATE: return "update";
143 case DT_SV_READ: return "read";
144 case DT_SV_DELETE: return "delete";
145 default: return "?";
146 }
147}
148
149// FNV-1a over a string, mixed with two ints. Used for synthetic stable keys.
150static uint64_t _fnv1a(const char *s, const int a, const int b)
151{
152 uint64_t h = 14695981039346656037ULL;
153 for(const char *p = s; p && *p; p++) { h ^= (unsigned char)*p; h *= 1099511628211ULL; }
154 h ^= (uint64_t)(uint32_t)a; h *= 1099511628211ULL;
155 h ^= (uint64_t)(uint32_t)b; h *= 1099511628211ULL;
156 // never collide with the INVALID sentinel
157 if(h == DT_PIXELPIPE_CACHE_HASH_INVALID) h ^= 1;
158 return h;
159}
160
161uint64_t dt_supervisor_node_key(const int pipe_type, const char *op_name, const int multi_priority)
162{
163 return _fnv1a(op_name ? op_name : "?", pipe_type, multi_priority);
164}
165
166static uint64_t _thumb_key(const int32_t imgid, const int mip)
167{
168 return _fnv1a("thumbnail", imgid, mip);
169}
170
171uint64_t dt_supervisor_thumbnail_key(const int32_t imgid, const int mip)
172{
173 return _thumb_key(imgid, mip);
174}
175
176static uint64_t _mipmap_key(const int32_t imgid, const int mip)
177{
178 return _fnv1a("mipmap", imgid, mip);
179}
180
181uint64_t dt_supervisor_mipmap_key(const int32_t imgid, const int mip)
182{
183 return _mipmap_key(imgid, mip);
184}
185
186static uint64_t _image_key(const int32_t imgid)
187{
188 return _fnv1a("image", imgid, 0);
189}
190
192{
193 return _image_key(imgid);
194}
195
196static uint64_t _form_key(const int formid)
197{
198 return _fnv1a("form", formid, 0);
199}
200
201// A cache-wait request is a runtime object (one queued GUI fetch), keyed by its
202// monotonic request id so each queue/serve/cancel of the same request folds onto
203// one entry, and successive requests from the same consumer are distinct objects.
204static uint64_t _cache_wait_key(const uint64_t request_id)
205{
206 return _fnv1a("cache-wait", (int)(request_id & 0xffffffffu), (int)(request_id >> 32));
207}
208
210{
211 return _form_key(formid);
212}
213
214static const char *_form_type_name(const int type)
215{
216 if(type & DT_MASKS_CIRCLE) return "circle";
217 if(type & DT_MASKS_ELLIPSE) return "ellipse";
218 if(type & DT_MASKS_POLYGON) return "path";
219 if(type & DT_MASKS_BRUSH) return "brush";
220 if(type & DT_MASKS_GRADIENT) return "gradient";
221 if(type & DT_MASKS_GROUP) return "group";
222 return "?";
223}
224
225// For a group form, the members (its `points` are group refs). Each member is an
226// object { id, hash } where hash is the member's form key, so the GUI can make it
227// a clickable link to the member form object.
228static JsonArray *_form_members_json(const dt_masks_form_t *form)
229{
230 if(!form || !(form->type & DT_MASKS_GROUP) || !form->points) return NULL;
231 JsonArray *a = json_array_new();
232 for(GList *p = form->points; p; p = g_list_next(p))
233 {
234 const dt_masks_form_group_t *g = (const dt_masks_form_group_t *)p->data;
235 if(!g) continue;
236 JsonObject *o = json_object_new();
237 json_object_set_int_member(o, "id", g->formid);
238 char buf[32];
239 g_snprintf(buf, sizeof(buf), "0x%016" G_GINT64_MODIFIER "x", _form_key(g->formid));
240 json_object_set_string_member(o, "hash", buf);
241 json_array_add_object_element(a, o);
242 }
243 return a;
244}
245
246// Inline list of a history item's attached forms (id, name, type, members).
247static JsonArray *_forms_json(GList *forms)
248{
249 if(!forms) return NULL;
250 JsonArray *out = json_array_new();
251 for(GList *f = forms; f; f = g_list_next(f))
252 {
253 const dt_masks_form_t *form = (const dt_masks_form_t *)f->data;
254 if(!form) continue;
255 JsonObject *o = json_object_new();
256 json_object_set_int_member(o, "id", form->formid);
257 char buf[32];
258 g_snprintf(buf, sizeof(buf), "0x%016" G_GINT64_MODIFIER "x", _form_key(form->formid));
259 json_object_set_string_member(o, "hash", buf); // clickable link to the form object
260 if(form->name[0]) json_object_set_string_member(o, "name", form->name);
261 json_object_set_string_member(o, "type", _form_type_name(form->type));
262 JsonArray *members = _form_members_json(form);
263 if(members) json_object_set_array_member(o, "members", members);
264 json_array_add_object_element(out, o);
265 }
266 return out;
267}
268
269// devid < 0 is the CPU path; otherwise an OpenCL device slot.
270static void _device_string(const int devid, char *out, const size_t out_size)
271{
272 if(devid < 0) g_strlcpy(out, "cpu", out_size);
273 else g_snprintf(out, out_size, "opencl-%d", devid);
274}
275
276static const char *_thread_tag(void)
277{
278 static __thread char tag[24];
279 g_snprintf(tag, sizeof(tag), "thread-%p", (void *)g_thread_self());
280 return tag;
281}
282
284{
285 if(_sv.inited) return;
286 _sv.entries = g_hash_table_new_full(g_int64_hash, g_int64_equal, g_free, g_free);
287 _sv.log = g_queue_new();
288 dt_pthread_mutex_init(&_sv.lock, NULL);
289 _sv.inited = TRUE;
290}
291
293{
294 if(!_sv.inited) return;
296 g_hash_table_destroy(_sv.entries);
297 _sv.entries = NULL;
298 g_queue_free_full(_sv.log, _logged_event_free);
299 _sv.log = NULL;
302 _sv.inited = FALSE;
303}
304
305void dt_supervisor_set_recording(const gboolean on)
306{
307 g_atomic_int_set(&dt_supervisor_recording, on ? 1 : 0);
308}
309
311{
312 if(!_sv.inited) return;
314 g_queue_free_full(_sv.log, _logged_event_free);
315 _sv.log = g_queue_new();
317}
318
319// Must be called with _sv.lock held. Returns the entry for `hash`, creating an
320// empty one if needed. *created is set TRUE when a fresh entry was allocated.
321static dt_sv_entry_t *_entry_get_locked(const uint64_t hash, gboolean *created)
322{
323 dt_sv_entry_t *e = (dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &hash);
324 if(e) { if(created) *created = FALSE; return e; }
325
326 e = (dt_sv_entry_t *)g_malloc0(sizeof(dt_sv_entry_t));
327 e->hash = hash;
328 e->alive = TRUE;
335 e->devid = -1;
336
337 uint64_t *key = (uint64_t *)g_malloc(sizeof(uint64_t));
338 *key = hash;
339 g_hash_table_insert(_sv.entries, key, e);
340 if(created) *created = TRUE;
341 return e;
342}
343
344// Apply the alive/resurrection bookkeeping for an op. Returns TRUE if this op
345// landed on a previously-deleted entry (reuse-after-delete).
346static gboolean _touch_alive_locked(dt_sv_entry_t *e, const gboolean created, const dt_sv_op_t op)
347{
348 if(op == DT_SV_DELETE)
349 {
350 e->alive = FALSE;
351 return FALSE;
352 }
353 const gboolean resurrected = !created && !e->alive;
354 e->alive = TRUE;
355 return resurrected;
356}
357
358// Domain string for an entry, by facet precedence (widget excluded: a widget
359// never owns a hash, it only annotates the buffer it consumes).
360static const char *_primary_domain(const dt_sv_entry_t *e)
361{
362 if(!e) return "unknown";
363 if(e->facets & DT_SV_F_CACHEWAIT) return "cache-wait";
364 if(e->facets & DT_SV_F_FORM) return "form";
365 if(e->facets & DT_SV_F_IMAGE) return "image";
366 if(e->facets & DT_SV_F_MIPMAP) return "mipmap";
367 if(e->facets & DT_SV_F_THUMB) return "thumbnail";
368 if(e->facets & DT_SV_F_BACKBUF) return "backbuf";
369 if(e->facets & DT_SV_F_CACHE) return "cacheline";
370 if(e->facets & DT_SV_F_NODE) return "node";
371 if(e->facets & DT_SV_F_HISTORY) return "history";
372 return "unknown";
373}
374
375// Pipe type to expose in the envelope: only for pipe-bound facets, 0/NONE omits.
376static int _entry_pipe_type(const dt_sv_entry_t *e)
377{
378 if(e && e->pipe_type && (e->facets & (DT_SV_F_NODE | DT_SV_F_CACHE | DT_SV_F_BACKBUF)))
379 return e->pipe_type;
380 return -1;
381}
382
383// "module/instance" label for an entry, or "?" when unknown.
384static void _module_label(const dt_sv_entry_t *e, char *out, const size_t out_size)
385{
386 if(e && e->op_name[0]) g_snprintf(out, out_size, "%s/%d", e->op_name, e->multi_priority);
387 else g_strlcpy(out, "?", out_size);
388}
389
390static void _set_hash_member(JsonObject *o, const char *name, const uint64_t hash)
391{
392 char buf[32];
393 g_snprintf(buf, sizeof(buf), "0x%016" G_GINT64_MODIFIER "x", hash);
394 json_object_set_string_member(o, name, buf);
395}
396
397// Shallow JSON description of the registry entry at `hash`, for a resolved
398// `input` / `params` / `consumes` edge. Called with _sv.lock held. NULL when
399// the hash is unset or unknown.
400static JsonObject *_resolve_locked(const uint64_t hash)
401{
402 if(!_hash_is_set(hash)) return NULL;
403 const dt_sv_entry_t *e = (dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &hash);
404 if(!e) return NULL;
405
406 JsonObject *o = json_object_new();
407 _set_hash_member(o, "hash", hash);
408
409 char module[128];
410 _module_label(e, module, sizeof(module));
411 if(strcmp(module, "?") != 0) json_object_set_string_member(o, "module", module);
412
413 if(e->facets & DT_SV_F_HISTORY)
414 {
415 json_object_set_int_member(o, "history_index", e->history_index);
416 json_object_set_boolean_member(o, "enabled", e->enabled);
417 }
418 if(e->facets & (DT_SV_F_NODE | DT_SV_F_CACHE)) json_object_set_int_member(o, "iop_order", e->iop_order);
419 json_object_set_boolean_member(o, "alive", e->alive);
420 return o;
421}
422
423static uint64_t _parse_hash(const char *s)
424{
425 if(!s) return 0;
426 return (uint64_t)g_ascii_strtoull(s, NULL, 0); // 0 base auto-detects the 0x prefix
427}
428
429// Build a retained event from the JSON root, extracting the navigable edges.
430static dt_sv_logged_event_t *_extract_event(JsonObject *root, const gchar *json_str)
431{
433 if(json_object_has_member(root, "ts")) e->ts = json_object_get_double_member(root, "ts");
434 if(json_object_has_member(root, "thread"))
435 g_strlcpy(e->thread, json_object_get_string_member(root, "thread"), sizeof(e->thread));
436 if(json_object_has_member(root, "op"))
437 g_strlcpy(e->op, json_object_get_string_member(root, "op"), sizeof(e->op));
438 if(json_object_has_member(root, "domain"))
439 g_strlcpy(e->domain, json_object_get_string_member(root, "domain"), sizeof(e->domain));
440 if(json_object_has_member(root, "hash"))
441 e->hash = _parse_hash(json_object_get_string_member(root, "hash"));
442 e->json = g_strdup(json_str);
443 e->links = g_array_new(FALSE, FALSE, sizeof(dt_sv_link_t));
444
445 // A short human mnemonic for the GUI row, picked per domain:
446 // - cacheline/node/history: module ("op/instance"); - widget: widget tag;
447 // - backbuf: pipe; - mipmap/image/thumbnail: image id.
448 const char *d = e->domain;
449 if(!g_strcmp0(d, "cacheline") && json_object_has_member(root, "name"))
450 g_strlcpy(e->cache_name, json_object_get_string_member(root, "name"), sizeof(e->cache_name));
451 if((!g_strcmp0(d, "cacheline") || !g_strcmp0(d, "node") || !g_strcmp0(d, "history"))
452 && json_object_has_member(root, "module"))
453 g_strlcpy(e->mnemonic, json_object_get_string_member(root, "module"), sizeof(e->mnemonic));
454 else if(!g_strcmp0(d, "widget") && json_object_has_member(root, "widget"))
455 g_strlcpy(e->mnemonic, json_object_get_string_member(root, "widget"), sizeof(e->mnemonic));
456 else if(!g_strcmp0(d, "backbuf") && json_object_has_member(root, "pipe"))
457 g_strlcpy(e->mnemonic, json_object_get_string_member(root, "pipe"), sizeof(e->mnemonic));
458 else if(!g_strcmp0(d, "form"))
459 {
460 const char *nm = json_object_has_member(root, "name") ? json_object_get_string_member(root, "name") : NULL;
461 const char *ty = json_object_has_member(root, "type") ? json_object_get_string_member(root, "type") : "";
462 const int id = json_object_has_member(root, "id") ? (int)json_object_get_int_member(root, "id") : 0;
463 if(nm && *nm) g_strlcpy(e->mnemonic, nm, sizeof(e->mnemonic));
464 else g_snprintf(e->mnemonic, sizeof(e->mnemonic), "#%d %s", id, ty);
465 }
466 else if(!g_strcmp0(d, "cache-wait"))
467 {
468 const char *owner = json_object_has_member(root, "owner") ? json_object_get_string_member(root, "owner") : NULL;
469 const char *target = json_object_has_member(root, "target") ? json_object_get_string_member(root, "target") : NULL;
470 if(owner && target) g_snprintf(e->mnemonic, sizeof(e->mnemonic), "%s→%s", owner, target);
471 else if(owner) g_strlcpy(e->mnemonic, owner, sizeof(e->mnemonic));
472 else if(target) g_strlcpy(e->mnemonic, target, sizeof(e->mnemonic));
473 }
474 else if((!g_strcmp0(d, "image") || !g_strcmp0(d, "mipmap")) && json_object_has_member(root, "filename"))
475 g_strlcpy(e->mnemonic, json_object_get_string_member(root, "filename"), sizeof(e->mnemonic));
476 else if((!g_strcmp0(d, "mipmap") || !g_strcmp0(d, "image") || !g_strcmp0(d, "thumbnail"))
477 && json_object_has_member(root, "imgid"))
478 g_snprintf(e->mnemonic, sizeof(e->mnemonic), "#%d", (int)json_object_get_int_member(root, "imgid"));
479
480 // nested-object edges carry the linked object id in their "hash" member
481 static const char *obj_edges[]
482 = { "params", "input", "node", "consumes", "mipmap", "image", "predecessor", "awaits", NULL };
483 for(int i = 0; obj_edges[i]; i++)
484 {
485 if(!json_object_has_member(root, obj_edges[i])) continue;
486 JsonObject *o = json_object_get_object_member(root, obj_edges[i]);
487 if(o && json_object_has_member(o, "hash"))
488 {
489 dt_sv_link_t lk = { 0 };
490 g_strlcpy(lk.label, obj_edges[i], sizeof(lk.label));
491 lk.hash = _parse_hash(json_object_get_string_member(o, "hash"));
492 g_array_append_val(e->links, lk);
493 }
494 }
495 // the rekey chain links are plain hash strings
496 static const char *str_edges[] = { "rekeyed_from", "rekeyed_to", "awaits_hash", NULL };
497 for(int i = 0; str_edges[i]; i++)
498 {
499 if(!json_object_has_member(root, str_edges[i])) continue;
500 dt_sv_link_t lk = { 0 };
501 g_strlcpy(lk.label, str_edges[i], sizeof(lk.label));
502 lk.hash = _parse_hash(json_object_get_string_member(root, str_edges[i]));
503 g_array_append_val(e->links, lk);
504 }
505
506 // array edges of linkable objects: a group form's `members`, and a history
507 // item's `forms` (each form linkable, plus its own nested group `members`).
508 static const char *arr_edges[] = { "members", "forms", NULL };
509 for(int i = 0; arr_edges[i]; i++)
510 {
511 if(!json_object_has_member(root, arr_edges[i])) continue;
512 const char *label = !g_strcmp0(arr_edges[i], "forms") ? "form" : "member";
513 JsonArray *arr = json_object_get_array_member(root, arr_edges[i]);
514 const guint n = arr ? json_array_get_length(arr) : 0;
515 for(guint j = 0; j < n; j++)
516 {
517 JsonNode *node = json_array_get_element(arr, j);
518 if(!JSON_NODE_HOLDS_OBJECT(node)) continue;
519 JsonObject *o = json_node_get_object(node);
520 if(json_object_has_member(o, "hash"))
521 {
522 dt_sv_link_t lk = { 0 };
523 g_strlcpy(lk.label, label, sizeof(lk.label));
524 lk.hash = _parse_hash(json_object_get_string_member(o, "hash"));
525 g_array_append_val(e->links, lk);
526 }
527 // a form entry may itself carry group members
528 if(json_object_has_member(o, "members"))
529 {
530 JsonArray *m = json_object_get_array_member(o, "members");
531 const guint mn = m ? json_array_get_length(m) : 0;
532 for(guint k = 0; k < mn; k++)
533 {
534 JsonNode *mnode = json_array_get_element(m, k);
535 if(!JSON_NODE_HOLDS_OBJECT(mnode)) continue;
536 JsonObject *mo = json_node_get_object(mnode);
537 if(!json_object_has_member(mo, "hash")) continue;
538 dt_sv_link_t lk = { 0 };
539 g_strlcpy(lk.label, "member", sizeof(lk.label));
540 lk.hash = _parse_hash(json_object_get_string_member(mo, "hash"));
541 g_array_append_val(e->links, lk);
542 }
543 }
544 }
545 }
546 return e;
547}
548
550{
552 if(_sv.log)
553 {
554 e->seq = ++_sv.next_seq;
555 g_queue_push_tail(_sv.log, e);
556 while(g_queue_get_length(_sv.log) > DT_SV_LOG_MAX)
557 _logged_event_free(g_queue_pop_head(_sv.log));
558 }
559 else
562}
563
564// Serialize `root` as one compact NDJSON line. Takes ownership of root. Dumps to
565// stderr when `-d supervisor` is set, and captures to the GUI log when recording.
566static void _emit_line(JsonObject *root)
567{
568 JsonNode *node = json_node_new(JSON_NODE_OBJECT);
569 json_node_take_object(node, root);
570 JsonGenerator *gen = json_generator_new();
571 json_generator_set_root(gen, node);
572 json_generator_set_pretty(gen, FALSE);
573
574 gsize len = 0;
575 gchar *str = json_generator_to_data(gen, &len);
576
578 {
579 fprintf(stderr, "%s\n", str);
580 fflush(stderr);
581 }
582 if(g_atomic_int_get(&dt_supervisor_recording))
583 _log_push(_extract_event(root, str));
584
585 g_free(str);
586 g_object_unref(gen);
587 json_node_free(node);
588}
589
590// Recursively render one introspection field's value as "path = value" strings
591// into `out`. Mirrors the offset handling of libs/history.c's tooltip walker but
592// dumps absolute values (not diffs).
593static void _introspect_dump(dt_introspection_field_t *field, const char *prefix, gpointer params,
594 JsonArray *out)
595{
596 if(!field) return;
597 void *p = (uint8_t *)params + field->header.offset;
598 const char *name = prefix ? prefix : "";
599
600 switch(field->header.type)
601 {
604 for(int i = 0; i < (int)field->Struct.entries; i++)
605 {
607 const char *nm = (e->header.description && *e->header.description) ? e->header.description
608 : e->header.field_name;
609 gchar *pre = prefix ? g_strdup_printf("%s.%s", prefix, nm) : g_strdup(nm);
610 _introspect_dump(e, pre, params, out); // struct fields share the params base
611 g_free(pre);
612 }
613 break;
616 {
617 char *s = (char *)p;
618 if(g_utf8_validate(s, -1, NULL))
619 {
620 gchar *t = g_strdup_printf("%s = \"%s\"", name, s);
621 json_array_add_string_element(out, t);
622 g_free(t);
623 }
624 }
625 else
626 {
627 for(int i = 0, off = 0; i < (int)field->Array.count && i < 8;
628 i++, off += field->Array.field->header.size)
629 {
630 gchar *pre = g_strdup_printf("%s[%d]", name, i);
631 _introspect_dump(field->Array.field, pre, (uint8_t *)params + off, out);
632 g_free(pre);
633 }
634 }
635 break;
637 {
638 gchar *t = g_strdup_printf("%s = %.4f", name, *(float *)p);
639 json_array_add_string_element(out, t);
640 g_free(t);
641 break;
642 }
644 {
645 gchar *t = g_strdup_printf("%s = %.4f", name, *(double *)p);
646 json_array_add_string_element(out, t);
647 g_free(t);
648 break;
649 }
652 {
653 const int val = field->header.type == DT_INTROSPECTION_TYPE_SHORT ? *(short *)p : *(int *)p;
654 gchar *t = g_strdup_printf("%s = %d", name, val);
655 json_array_add_string_element(out, t);
656 g_free(t);
657 break;
658 }
661 {
662 const unsigned val = field->header.type == DT_INTROSPECTION_TYPE_USHORT ? *(unsigned short *)p
663 : *(unsigned int *)p;
664 gchar *t = g_strdup_printf("%s = %u", name, val);
665 json_array_add_string_element(out, t);
666 g_free(t);
667 break;
668 }
670 {
671 gchar *t = g_strdup_printf("%s = %d", name, (int)*(int8_t *)p);
672 json_array_add_string_element(out, t);
673 g_free(t);
674 break;
675 }
677 {
678 gchar *t = g_strdup_printf("%s = %u", name, (unsigned)*(uint8_t *)p);
679 json_array_add_string_element(out, t);
680 g_free(t);
681 break;
682 }
684 {
685 gchar *t = g_strdup_printf("%s = %s", name, *(gboolean *)p ? "on" : "off");
686 json_array_add_string_element(out, t);
687 g_free(t);
688 break;
689 }
691 {
692 const int val = *(int *)p;
693 const char *str = "?";
694 for(dt_introspection_type_enum_tuple_t *i = field->Enum.values; i && i->name; i++)
695 if(i->value == val)
696 {
697 str = (i->description && *i->description) ? i->description : i->name;
698 break;
699 }
700 gchar *t = g_strdup_printf("%s = %s", name, str);
701 json_array_add_string_element(out, t);
702 g_free(t);
703 break;
704 }
705 default:
706 break; // OPAQUE / LONG / FLOATCOMPLEX etc. are not rendered
707 }
708}
709
710// Build a "parameters" JSON array (human-legible) from a module's introspection.
711static JsonArray *_params_json(const dt_iop_module_t *module, const void *params)
712{
713 if(!module || !params || !module->have_introspection || !module->get_introspection) return NULL;
714 dt_introspection_t *intro = module->get_introspection();
715 if(!intro || !intro->field) return NULL;
716 JsonArray *out = json_array_new();
717 _introspect_dump(intro->field, NULL, (gpointer)params, out);
718 if(json_array_get_length(out) == 0)
719 {
720 json_array_unref(out);
721 return NULL;
722 }
723 return out;
724}
725
726// "name = value" line into a JSON string array.
727static void _arr_addf(JsonArray *out, const char *fmt, ...) G_GNUC_PRINTF(2, 3);
728static void _arr_addf(JsonArray *out, const char *fmt, ...)
729{
730 va_list ap;
731 va_start(ap, fmt);
732 gchar *s = g_strdup_vprintf(fmt, ap);
733 va_end(ap);
734 json_array_add_string_element(out, s);
735 g_free(s);
736}
737
738static const char *_name_for_value(const dt_develop_name_value_t *list, const int value)
739{
740 for(const dt_develop_name_value_t *i = list; i && i->name[0]; i++)
741 if(i->value == value) return i->name;
742 return NULL;
743}
744
745static void _arr_add_enum(JsonArray *out, const char *label, const dt_develop_name_value_t *names,
746 const int value)
747{
748 const char *n = _name_for_value(names, value);
749 if(n) _arr_addf(out, "%s = %s", label, _(n));
750 else _arr_addf(out, "%s = %d", label, value);
751}
752
753// Render the blend (masking) parameters human-legibly, mirroring the fields
754// libs/history.c shows in its tooltips. NULL when blending is disabled.
755static JsonArray *_blendop_json(const dt_develop_blend_params_t *bp)
756{
757 if(!bp || bp->mask_mode == DEVELOP_MASK_DISABLED) return NULL;
758
759 JsonArray *out = json_array_new();
764 _arr_addf(out, "blend fulcrum = %.2f EV", bp->blend_parameter);
765 _arr_addf(out, "opacity = %.4f", bp->opacity);
768 _arr_addf(out, "feathering radius = %.4f", bp->feathering_radius);
770 _arr_addf(out, "mask blur = %.4f", bp->blur_radius);
771 _arr_addf(out, "mask contrast = %.4f", bp->contrast);
772 _arr_addf(out, "brightness = %.4f", bp->brightness);
773 _arr_addf(out, "details = %.4f", bp->details);
774 if(bp->mask_id) _arr_addf(out, "drawn mask id = %d", bp->mask_id);
775 if(bp->raster_mask_id)
776 {
777 _arr_addf(out, "raster mask instance = %d", bp->raster_mask_instance);
778 _arr_addf(out, "raster mask id = %d", bp->raster_mask_id);
780 }
781 return out;
782}
783
784// Common envelope shared by every event. `resurrected` adds the reuse-after-
785// delete marker. Pass pipe_type < 0 to omit the pipe field.
786static JsonObject *_envelope(const dt_sv_op_t op, const char *domain, const uint64_t hash,
787 const int pipe_type, const int32_t imgid, const gboolean alive,
788 const gboolean resurrected)
789{
790 JsonObject *o = json_object_new();
791 json_object_set_double_member(o, "ts", dt_get_wtime() - darktable.start_wtime);
792 json_object_set_string_member(o, "thread", _thread_tag());
793 json_object_set_string_member(o, "op", _op_str(op));
794 json_object_set_string_member(o, "domain", domain);
795 if(pipe_type >= 0)
796 json_object_set_string_member(o, "pipe", dt_pixelpipe_get_pipe_name((dt_dev_pixelpipe_type_t)pipe_type));
797 if(imgid > 0) json_object_set_int_member(o, "imgid", imgid);
798 _set_hash_member(o, "hash", hash);
799 json_object_set_boolean_member(o, "alive", alive);
800 if(resurrected) json_object_set_boolean_member(o, "resurrected", TRUE);
801 return o;
802}
803
804void dt_supervisor_history(const dt_sv_op_t op, const uint64_t param_hash, const char *op_name,
805 const int multi_priority, const char *multi_name, const int iop_order,
806 const int history_index, const int32_t imgid, const gboolean enabled,
807 const dt_iop_module_t *module, const void *params,
808 const dt_develop_blend_params_t *blend_params, GList *forms)
809{
810 if(!dt_supervisor_active() || !_sv.inited) return;
811
812 // Render params/blendops/forms outside the lock (they only read caller data).
813 JsonArray *parameters = _params_json(module, params);
814 JsonArray *blendop = _blendop_json(blend_params);
815 JsonArray *forms_json = _forms_json(forms);
816 // Register each attached form as its own object (each call locks independently).
817 for(GList *f = forms; f; f = g_list_next(f)) dt_supervisor_form(DT_SV_UPDATE, f->data);
818
820 gboolean created;
821 dt_sv_entry_t *e = _entry_get_locked(param_hash, &created);
823 if(op_name) g_strlcpy(e->op_name, op_name, sizeof(e->op_name));
824 if(multi_name) g_strlcpy(e->multi_name, multi_name, sizeof(e->multi_name));
825 e->multi_priority = multi_priority;
826 e->iop_order = iop_order;
827 e->history_index = history_index;
828 e->imgid = imgid;
829 e->enabled = enabled;
830 const gboolean resurrected = _touch_alive_locked(e, created, op);
831
832 // history is not tied to a pipeline: pass -1 so the envelope omits the pipe field
833 JsonObject *root = _envelope(op, "history", param_hash, -1, imgid, e->alive, resurrected);
834 char module_label[128];
835 _module_label(e, module_label, sizeof(module_label));
836 json_object_set_string_member(root, "module", module_label);
837 json_object_set_int_member(root, "iop_order", iop_order);
838 json_object_set_int_member(root, "history_index", history_index);
839 json_object_set_boolean_member(root, "enabled", enabled);
840 if(parameters) json_object_set_array_member(root, "parameters", parameters);
841 if(blendop) json_object_set_array_member(root, "blendop", blendop);
842 if(forms_json) json_object_set_array_member(root, "forms", forms_json);
843 // Hold the image filename (borrowed from the registered image object).
844 if(imgid > 0)
845 {
846 const uint64_t image_key = _image_key(imgid);
847 const dt_sv_entry_t *image_e = (const dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &image_key);
848 if(image_e && image_e->img_filename[0])
849 json_object_set_string_member(root, "filename", image_e->img_filename);
850 }
852
853 _emit_line(root);
854}
855
856void dt_supervisor_node(const dt_sv_op_t op, const uint64_t node_hash, const uint64_t param_hash,
857 const uint64_t predecessor_hash, const char *op_name, const int multi_priority,
858 const int iop_order, const int pipe_type, const int32_t imgid)
859{
860 if(!dt_supervisor_active() || !_sv.inited) return;
861
863 gboolean created;
864 dt_sv_entry_t *e = _entry_get_locked(node_hash, &created);
865 e->facets |= DT_SV_F_NODE;
866 if(op_name) g_strlcpy(e->op_name, op_name, sizeof(e->op_name));
867 e->multi_priority = multi_priority;
868 e->iop_order = iop_order;
869 e->pipe_type = pipe_type;
870 e->imgid = imgid;
871 // Bind the node to its history item once synchronized (param_hash set).
872 if(_hash_is_set(param_hash)) e->param_hash = param_hash;
873 // Reference the predecessor node in the pipeline (set at topology creation).
874 if(_hash_is_set(predecessor_hash)) e->pred_hash = predecessor_hash;
875 const gboolean resurrected = _touch_alive_locked(e, created, op);
876
877 JsonObject *root = _envelope(op, "node", node_hash, pipe_type, imgid, e->alive, resurrected);
878 char module[128];
879 _module_label(e, module, sizeof(module));
880 json_object_set_string_member(root, "module", module);
881 json_object_set_int_member(root, "iop_order", iop_order);
882 JsonObject *params = _resolve_locked(e->param_hash);
883 if(params) json_object_set_object_member(root, "params", params);
884 JsonObject *pred = _resolve_locked(e->pred_hash);
885 if(pred) json_object_set_object_member(root, "predecessor", pred);
887
888 _emit_line(root);
889}
890
891void dt_supervisor_cacheline_create(const uint64_t hash, const uint64_t node_hash,
892 const uint64_t param_hash, const uint64_t input_hash,
893 const char *op_name, const int multi_priority, const int iop_order,
894 const int pipe_type, const int32_t imgid, const int roi_w,
895 const int roi_h, const int devid, const size_t size,
896 const char *name)
897{
898 if(!dt_supervisor_active() || !_sv.inited) return;
899
901 gboolean created;
902 dt_sv_entry_t *e = _entry_get_locked(hash, &created);
903 e->facets |= DT_SV_F_CACHE;
904 e->node_hash = node_hash;
905 // Prefer the producing node's history binding (set at synchronization) over the
906 // passed piece->hash: the latter folds in runtime_data_hash() for some modules
907 // and then no longer matches the history entry key. Fall back when the node is
908 // not bound yet (e.g. default-param node, or recording started mid-session).
909 const dt_sv_entry_t *node_e = (const dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &node_hash);
910 e->param_hash = (node_e && _hash_is_set(node_e->param_hash)) ? node_e->param_hash : param_hash;
911 e->input_hash = input_hash;
912 if(op_name) g_strlcpy(e->op_name, op_name, sizeof(e->op_name));
913 e->multi_priority = multi_priority;
914 e->iop_order = iop_order;
915 e->pipe_type = pipe_type;
916 e->imgid = imgid;
917 e->roi_w = roi_w;
918 e->roi_h = roi_h;
919 e->devid = devid;
920 e->size = size;
921 if(name) g_strlcpy(e->cl_name, name, sizeof(e->cl_name));
922 const gboolean resurrected = _touch_alive_locked(e, created, DT_SV_CREATE);
923
924 JsonObject *root = _envelope(DT_SV_CREATE, "cacheline", hash, pipe_type, imgid, e->alive, resurrected);
925 char module[128];
926 _module_label(e, module, sizeof(module));
927 json_object_set_string_member(root, "module", module);
928 json_object_set_int_member(root, "iop_order", iop_order);
929 json_object_set_int_member(root, "size", (gint64)size);
930 if(e->cl_name[0]) json_object_set_string_member(root, "name", e->cl_name);
931
932 char dev[32];
933 _device_string(devid, dev, sizeof(dev));
934 json_object_set_string_member(root, "device", dev);
935
936 JsonArray *roi = json_array_new();
937 json_array_add_int_element(roi, roi_w);
938 json_array_add_int_element(roi, roi_h);
939 json_object_set_array_member(root, "roi", roi);
940
941 JsonObject *params = _resolve_locked(e->param_hash); // node-bound history key
942 if(params) json_object_set_object_member(root, "params", params);
943 JsonObject *in = _resolve_locked(input_hash);
944 if(in) json_object_set_object_member(root, "input", in);
945 JsonObject *node = _resolve_locked(node_hash);
946 if(node) json_object_set_object_member(root, "node", node);
948
949 _emit_line(root);
950}
951
952void dt_supervisor_cacheline_read(const uint64_t hash, const size_t size)
953{
954 if(!dt_supervisor_active() || !_sv.inited) return;
955
957 gboolean created;
958 dt_sv_entry_t *e = _entry_get_locked(hash, &created);
959 e->facets |= DT_SV_F_CACHE;
960 if(size) e->size = size;
961 const gboolean resurrected = _touch_alive_locked(e, created, DT_SV_READ);
962
963 JsonObject *root = _envelope(DT_SV_READ, "cacheline", hash, e->pipe_type ? e->pipe_type : -1,
964 e->imgid, e->alive, resurrected);
965 json_object_set_int_member(root, "size", (gint64)e->size);
966 char module[128];
967 _module_label(e, module, sizeof(module));
968 if(strcmp(module, "?") != 0) json_object_set_string_member(root, "module", module);
969 if(e->cl_name[0]) json_object_set_string_member(root, "name", e->cl_name);
970 // Expose the full linkage so every related hash is clickable from a read too.
971 JsonObject *params = _resolve_locked(e->param_hash);
972 if(params) json_object_set_object_member(root, "params", params);
973 JsonObject *node = _resolve_locked(e->node_hash);
974 if(node) json_object_set_object_member(root, "node", node);
975 JsonObject *in = _resolve_locked(e->input_hash);
976 if(in) json_object_set_object_member(root, "input", in);
978
979 _emit_line(root);
980}
981
982void dt_supervisor_cacheline_delete(const uint64_t hash, const size_t size, const int owner_pipe_id,
983 const char *name)
984{
985 if(!dt_supervisor_active() || !_sv.inited) return;
986
988 gboolean created;
989 dt_sv_entry_t *e = _entry_get_locked(hash, &created);
990 e->facets |= DT_SV_F_CACHE;
991 if(size) e->size = size;
992 if(owner_pipe_id) e->owner_pipe_id = owner_pipe_id;
993 if(name) g_strlcpy(e->cl_name, name, sizeof(e->cl_name));
995
996 JsonObject *root = _envelope(DT_SV_DELETE, "cacheline", hash, e->pipe_type ? e->pipe_type : -1,
997 e->imgid, e->alive, FALSE);
998 json_object_set_int_member(root, "size", (gint64)e->size);
999 if(e->cl_name[0]) json_object_set_string_member(root, "name", e->cl_name);
1000 json_object_set_int_member(root, "owner_pipe", e->owner_pipe_id);
1001 char module[128];
1002 _module_label(e, module, sizeof(module));
1003 if(strcmp(module, "?") != 0) json_object_set_string_member(root, "module", module);
1004 JsonObject *params = _resolve_locked(e->param_hash);
1005 if(params) json_object_set_object_member(root, "params", params);
1006 JsonObject *node = _resolve_locked(e->node_hash);
1007 if(node) json_object_set_object_member(root, "node", node);
1009
1010 _emit_line(root);
1011}
1012
1013void dt_supervisor_cache_wait(const dt_sv_op_t op, const uint64_t request_id, const uint64_t awaited_hash,
1014 const char *owner_tag, const char *op_name, const int multi_priority,
1015 const int pipe_type, const int32_t imgid, const gboolean request_emitted,
1016 const char *note)
1017{
1018 if(!dt_supervisor_active() || !_sv.inited) return;
1019
1020 const uint64_t key = _cache_wait_key(request_id);
1021
1023 gboolean created;
1024 dt_sv_entry_t *e = _entry_get_locked(key, &created);
1026 e->request_id = request_id;
1027 if(op_name) g_strlcpy(e->op_name, op_name, sizeof(e->op_name));
1028 e->multi_priority = multi_priority;
1029 e->pipe_type = pipe_type;
1030 if(imgid > 0) e->imgid = imgid;
1031 if(owner_tag) g_strlcpy(e->owner_tag, owner_tag, sizeof(e->owner_tag));
1032 if(_hash_is_set(awaited_hash)) e->awaits_hash = awaited_hash;
1033 e->request_emitted = request_emitted;
1034 const gboolean resurrected = _touch_alive_locked(e, created, op);
1035
1036 // A cache-wait is not pipe-bound in the cacheline sense, but the target pipe is
1037 // useful context; _entry_pipe_type() only surfaces it for cache/node/backbuf, so
1038 // pass it explicitly here.
1039 JsonObject *root = _envelope(op, "cache-wait", key, pipe_type, e->imgid, e->alive, resurrected);
1040 json_object_set_int_member(root, "request_id", (gint64)request_id);
1041 if(e->owner_tag[0]) json_object_set_string_member(root, "owner", e->owner_tag);
1042 char module[128];
1043 _module_label(e, module, sizeof(module));
1044 if(strcmp(module, "?") != 0) json_object_set_string_member(root, "target", module);
1045 json_object_set_boolean_member(root, "request_emitted", request_emitted);
1046 if(note) json_object_set_string_member(root, "note", note);
1047
1048 // Link to the awaited cacheline. When it resolves, the wait is chained to a real
1049 // output object (walk cache-wait -> awaits -> cacheline). When it does NOT resolve
1050 // (no cacheline was ever published under that hash), fall back to the raw hash so
1051 // the "order to grab a cacheline never processed" case stays searchable — that
1052 // absence is the signature of a producer/consumer hash mismatch.
1053 JsonObject *awaits = _resolve_locked(e->awaits_hash);
1054 if(awaits) json_object_set_object_member(root, "awaits", awaits);
1055 else if(_hash_is_set(e->awaits_hash)) _set_hash_member(root, "awaits_hash", e->awaits_hash);
1057
1058 _emit_line(root);
1059}
1060
1061// Build one side of a rekey record (called with _sv.lock held).
1062static JsonObject *_rekey_record(const dt_sv_op_t op, const dt_sv_entry_t *e, const uint64_t hash,
1063 const char *link_field, const uint64_t link_hash, const gboolean alive)
1064{
1065 JsonObject *o = _envelope(op, _primary_domain(e), hash, _entry_pipe_type(e), e->imgid, alive, FALSE);
1066 char module[128];
1067 _module_label(e, module, sizeof(module));
1068 if(strcmp(module, "?") != 0) json_object_set_string_member(o, "module", module);
1069 _set_hash_member(o, link_field, link_hash);
1070 // Expose the history item that triggered the rekey, through the node, as
1071 // clickable links (params is taken from the node's current binding).
1072 JsonObject *node = _resolve_locked(e->node_hash);
1073 if(node) json_object_set_object_member(o, "node", node);
1074 JsonObject *params = _resolve_locked(e->param_hash);
1075 if(params) json_object_set_object_member(o, "params", params);
1076 return o;
1077}
1078
1079void dt_supervisor_rekey(const uint64_t old_hash, const uint64_t new_hash)
1080{
1081 if(!dt_supervisor_active() || !_sv.inited) return;
1082 if(!_hash_is_set(old_hash) || !_hash_is_set(new_hash) || old_hash == new_hash) return;
1083
1085 dt_sv_entry_t *old = (dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &old_hash);
1086 gboolean created;
1087 dt_sv_entry_t *new_e = _entry_get_locked(new_hash, &created);
1088
1089 if(old)
1090 {
1091 // The new key is the same logical object: inherit the old metadata.
1092 new_e->facets |= old->facets;
1093 new_e->param_hash = old->param_hash;
1094 new_e->input_hash = old->input_hash;
1095 new_e->node_hash = old->node_hash;
1096 new_e->history_hash = old->history_hash;
1097 g_strlcpy(new_e->op_name, old->op_name, sizeof(new_e->op_name));
1098 g_strlcpy(new_e->multi_name, old->multi_name, sizeof(new_e->multi_name));
1099 new_e->multi_priority = old->multi_priority;
1100 new_e->iop_order = old->iop_order;
1101 new_e->history_index = old->history_index;
1102 new_e->pipe_type = old->pipe_type;
1103 new_e->imgid = old->imgid;
1104 new_e->roi_w = old->roi_w;
1105 new_e->roi_h = old->roi_h;
1106 new_e->devid = old->devid;
1107 new_e->enabled = old->enabled;
1108 new_e->size = old->size;
1109 g_strlcpy(new_e->cl_name, old->cl_name, sizeof(new_e->cl_name));
1110 new_e->bb_w = old->bb_w;
1111 new_e->bb_h = old->bb_h;
1112 new_e->bb_bpp = old->bb_bpp;
1113 old->alive = FALSE;
1114 }
1115 // A rekey is triggered by a parameter change. The producing node was bound to
1116 // the new history item at synchronization (before this rekey), so take the
1117 // history from the node rather than inheriting the old cacheline's stale param.
1118 const dt_sv_entry_t *node_e = _hash_is_set(new_e->node_hash)
1119 ? (const dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &new_e->node_hash)
1120 : NULL;
1121 if(node_e && _hash_is_set(node_e->param_hash)) new_e->param_hash = node_e->param_hash;
1122 new_e->alive = TRUE;
1123
1124 JsonObject *old_rec = old ? _rekey_record(DT_SV_DELETE, old, old_hash, "rekeyed_to", new_hash, FALSE) : NULL;
1125 JsonObject *new_rec = _rekey_record(DT_SV_CREATE, new_e, new_hash, "rekeyed_from", old_hash, TRUE);
1127
1128 if(old_rec) _emit_line(old_rec);
1129 _emit_line(new_rec);
1130}
1131
1132void dt_supervisor_backbuf(const dt_sv_op_t op, const uint64_t hash, const uint64_t history_hash,
1133 const int w, const int h, const int bpp, const int pipe_type,
1134 const int devid)
1135{
1136 if(!dt_supervisor_active() || !_sv.inited) return;
1137
1139 gboolean created;
1140 dt_sv_entry_t *e = _entry_get_locked(hash, &created);
1141 e->facets |= DT_SV_F_BACKBUF;
1142 e->history_hash = history_hash;
1143 e->bb_w = w;
1144 e->bb_h = h;
1145 e->bb_bpp = bpp;
1146 e->pipe_type = pipe_type;
1147 if(devid >= 0) e->devid = devid;
1148 const gboolean resurrected = _touch_alive_locked(e, created, op);
1149
1150 JsonObject *root = _envelope(op, "backbuf", hash, pipe_type, e->imgid, e->alive, resurrected);
1151 char dev[32];
1152 _device_string(e->devid, dev, sizeof(dev));
1153 json_object_set_string_member(root, "device", dev);
1154 _set_hash_member(root, "history_hash", history_hash);
1155
1156 JsonArray *size = json_array_new();
1157 json_array_add_int_element(size, w);
1158 json_array_add_int_element(size, h);
1159 json_array_add_int_element(size, bpp);
1160 json_object_set_array_member(root, "size", size);
1161
1162 // The backbuf hash is the producing node output hash, so it shares this entry.
1163 char module[128];
1164 _module_label(e, module, sizeof(module));
1165 if(strcmp(module, "?") != 0) json_object_set_string_member(root, "module", module);
1166 JsonObject *params = _resolve_locked(e->param_hash);
1167 if(params) json_object_set_object_member(root, "params", params);
1169
1170 _emit_line(root);
1171}
1172
1173void dt_supervisor_widget(const dt_sv_op_t op, const char *widget_tag, const uint64_t consumed_hash,
1174 const int pipe_type, const int32_t imgid)
1175{
1176 if(!dt_supervisor_active() || !_sv.inited) return;
1177
1179 // The widget does not own `consumed_hash`; only annotate the buffer entry.
1180 dt_sv_entry_t *src = (dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &consumed_hash);
1181 if(src) src->facets |= DT_SV_F_WIDGET;
1182
1183 JsonObject *root = _envelope(op, "widget", consumed_hash, pipe_type, imgid,
1184 src ? src->alive : TRUE, FALSE);
1185 if(widget_tag) json_object_set_string_member(root, "widget", widget_tag);
1186 JsonObject *consumed = _resolve_locked(consumed_hash);
1187 if(consumed) json_object_set_object_member(root, "consumes", consumed);
1188 JsonObject *params = src ? _resolve_locked(src->param_hash) : NULL;
1189 if(params) json_object_set_object_member(root, "params", params);
1191
1192 _emit_line(root);
1193}
1194
1195void dt_supervisor_thumbnail(const dt_sv_op_t op, const int32_t imgid, const int width,
1196 const int height, const int mip, const gboolean success)
1197{
1198 if(!dt_supervisor_active() || !_sv.inited) return;
1199
1200 const uint64_t key = _thumb_key(imgid, mip);
1202 gboolean created;
1203 dt_sv_entry_t *e = _entry_get_locked(key, &created);
1204 e->facets |= DT_SV_F_THUMB;
1205 e->imgid = imgid;
1206 e->roi_w = width;
1207 e->roi_h = height;
1208 e->mip = mip;
1209 e->thumb_success = success;
1210 const gboolean resurrected = _touch_alive_locked(e, created, op);
1211
1212 JsonObject *root = _envelope(op, "thumbnail", key, -1, imgid, e->alive, resurrected);
1213 json_object_set_int_member(root, "mip", mip);
1214 json_object_set_boolean_member(root, "success", success);
1215 JsonArray *size = json_array_new();
1216 json_array_add_int_element(size, width);
1217 json_array_add_int_element(size, height);
1218 json_object_set_array_member(root, "size", size);
1219
1220 // Reference the mipmap object this thumbnail displays (by imgid + mip), so the
1221 // displayed mipmap hash is visible and clickable.
1222 const uint64_t mk = _mipmap_key(imgid, mip);
1223 JsonObject *mm = json_object_new();
1224 _set_hash_member(mm, "hash", mk);
1225 json_object_set_int_member(mm, "mip", mip);
1226 const dt_sv_entry_t *me = (const dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &mk);
1227 if(me)
1228 {
1229 json_object_set_boolean_member(mm, "alive", me->alive);
1230 if(me->img_filename[0]) json_object_set_string_member(mm, "filename", me->img_filename);
1231 }
1232 json_object_set_object_member(root, "mipmap", mm);
1234
1235 _emit_line(root);
1236}
1237
1238// A curated subset of dt_image_t: the mipmap object's "properties".
1239static JsonObject *_image_properties_json(const dt_image_t *img)
1240{
1241 JsonObject *o = json_object_new();
1242 json_object_set_int_member(o, "id", img->id);
1243 json_object_set_string_member(o, "filename", img->filename);
1244 json_object_set_string_member(o, "folder", img->folder);
1245 json_object_set_int_member(o, "film_id", img->film_id);
1246 json_object_set_int_member(o, "group_id", img->group_id);
1247 json_object_set_int_member(o, "version", img->version);
1248
1249 JsonArray *dim = json_array_new();
1250 json_array_add_int_element(dim, img->width);
1251 json_array_add_int_element(dim, img->height);
1252 json_object_set_array_member(o, "dimensions", dim);
1253 JsonArray *pdim = json_array_new();
1254 json_array_add_int_element(pdim, img->p_width);
1255 json_array_add_int_element(pdim, img->p_height);
1256 json_object_set_array_member(o, "processed", pdim);
1257
1258 char flags[16];
1259 g_snprintf(flags, sizeof(flags), "0x%08x", (unsigned)img->flags);
1260 json_object_set_string_member(o, "flags", flags);
1261 json_object_set_int_member(o, "rating", img->flags & 0x7);
1262 json_object_set_int_member(o, "orientation", (int)img->orientation);
1263 json_object_set_int_member(o, "loader", (int)img->loader);
1264
1265 json_object_set_string_member(o, "camera", img->camera_makermodel);
1266 json_object_set_string_member(o, "lens", img->exif_lens);
1267 json_object_set_string_member(o, "datetime", img->datetime);
1268 json_object_set_double_member(o, "iso", img->exif_iso);
1269 json_object_set_double_member(o, "aperture", img->exif_aperture);
1270 json_object_set_double_member(o, "exposure", img->exif_exposure);
1271 json_object_set_double_member(o, "focal_length", img->exif_focal_length);
1272 return o;
1273}
1274
1275void dt_supervisor_mipmap(const dt_sv_op_t op, const int32_t imgid, const int mip)
1276{
1277 if(!dt_supervisor_active() || !_sv.inited) return;
1278
1279 const uint64_t key = _mipmap_key(imgid, mip);
1280 const uint64_t image_key = _image_key(imgid);
1282 gboolean created;
1283 dt_sv_entry_t *e = _entry_get_locked(key, &created);
1284 e->facets |= DT_SV_F_MIPMAP;
1285 e->imgid = imgid;
1286 e->mip = mip;
1287 e->image_hash = image_key;
1288 const gboolean resurrected = _touch_alive_locked(e, created, op);
1289
1290 JsonObject *root = _envelope(op, "mipmap", key, -1, imgid, e->alive, resurrected);
1291 json_object_set_int_member(root, "mip", mip);
1292 // Link to the image cache object instead of duplicating its dt_image_t values.
1293 // Borrow only the filename (for the row mnemonic) from the linked image entry.
1294 const dt_sv_entry_t *image_e = (const dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &image_key);
1295 if(image_e && image_e->img_filename[0])
1296 json_object_set_string_member(root, "filename", image_e->img_filename);
1297 JsonObject *im = _resolve_locked(image_key);
1298 if(!im)
1299 {
1300 im = json_object_new();
1301 _set_hash_member(im, "hash", image_key);
1302 }
1303 json_object_set_object_member(root, "image", im);
1305
1306 _emit_line(root);
1307}
1308
1309void dt_supervisor_image(const dt_sv_op_t op, const int32_t imgid, const dt_image_t *img)
1310{
1311 if(!dt_supervisor_active() || !_sv.inited) return;
1312
1313 const uint64_t key = _image_key(imgid);
1315 gboolean created;
1316 dt_sv_entry_t *e = _entry_get_locked(key, &created);
1317 e->facets |= DT_SV_F_IMAGE;
1318 e->imgid = imgid;
1319 if(img && img->filename[0]) g_strlcpy(e->img_filename, img->filename, sizeof(e->img_filename));
1320 // The image cache `allocate` callback (the natural create point) only fires on
1321 // a miss, but images are usually already cached when a consumer (e.g. a mipmap)
1322 // first references them. So the first time we see an image, treat it as a
1323 // create regardless of the caller's intent, so it exists for navigation.
1324 const dt_sv_op_t eff_op = created ? DT_SV_CREATE : op;
1325 const gboolean resurrected = _touch_alive_locked(e, created, eff_op);
1326
1327 JsonObject *root = _envelope(eff_op, "image", key, -1, imgid, e->alive, resurrected);
1328 if(e->img_filename[0]) json_object_set_string_member(root, "filename", e->img_filename);
1329 if(img) json_object_set_object_member(root, "properties", _image_properties_json(img));
1331
1332 _emit_line(root);
1333}
1334
1336{
1337 if(!dt_supervisor_active() || !_sv.inited || !form) return;
1338
1339 const uint64_t key = _form_key(form->formid);
1340 // Build the group members outside the lock (read-only walk of form->points).
1341 JsonArray *members = _form_members_json(form);
1342
1344 gboolean created;
1345 dt_sv_entry_t *e = _entry_get_locked(key, &created);
1346 e->facets |= DT_SV_F_FORM;
1347 e->formid = form->formid;
1348 e->form_type = form->type;
1349 if(form->name[0]) g_strlcpy(e->form_name, form->name, sizeof(e->form_name));
1350 // First sighting is a create regardless of the caller's intent (a form may be
1351 // seen first via a history snapshot rather than its allocation).
1352 const dt_sv_op_t eff_op = created ? DT_SV_CREATE : op;
1353 const gboolean resurrected = _touch_alive_locked(e, created, eff_op);
1354
1355 JsonObject *root = _envelope(eff_op, "form", key, -1, -1, e->alive, resurrected);
1356 json_object_set_int_member(root, "id", form->formid);
1357 if(e->form_name[0]) json_object_set_string_member(root, "name", e->form_name);
1358 json_object_set_string_member(root, "type", _form_type_name(form->type));
1359 json_object_set_int_member(root, "version", form->version);
1360 if(members) json_object_set_array_member(root, "members", members);
1362
1363 _emit_line(root);
1364}
1365
1366// Append " 0xHASH (module)" for a resolved edge, marking dead entries.
1367static void _describe_edge(GString *s, const char *prefix, const uint64_t hash)
1368{
1369 if(!_hash_is_set(hash)) return;
1370 const dt_sv_entry_t *e = (dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &hash);
1371 if(!e) return;
1372 char module[128];
1373 _module_label(e, module, sizeof(module));
1374 g_string_append_printf(s, "%s 0x%016" G_GINT64_MODIFIER "x", prefix, hash);
1375 if(strcmp(module, "?") != 0) g_string_append_printf(s, " (%s)", module);
1376 if(!e->alive) g_string_append(s, " [deleted]");
1377}
1378
1380{
1381 if(!dt_supervisor_active() || !_sv.inited) return NULL;
1382
1384 const dt_sv_entry_t *e = (dt_sv_entry_t *)g_hash_table_lookup(_sv.entries, &hash);
1385 if(!e)
1386 {
1388 return NULL;
1389 }
1390
1391 GString *s = g_string_new(NULL);
1392 char module[128];
1393 _module_label(e, module, sizeof(module));
1394 const char *pipe = (e->pipe_type >= 0 && (e->facets & (DT_SV_F_NODE | DT_SV_F_CACHE | DT_SV_F_BACKBUF)))
1396 : NULL;
1397 char dev[32];
1398 _device_string(e->devid, dev, sizeof(dev));
1399
1400 if(e->facets & DT_SV_F_FORM)
1401 {
1402 g_string_append_printf(s, "form #%d %s", e->formid, _form_type_name(e->form_type));
1403 if(e->form_name[0]) g_string_append_printf(s, " (%s)", e->form_name);
1404 }
1405 else if(e->facets & DT_SV_F_IMAGE)
1406 {
1407 g_string_append_printf(s, "image #%d", e->imgid);
1408 if(e->img_filename[0]) g_string_append_printf(s, " (%s)", e->img_filename);
1409 }
1410 else if(e->facets & DT_SV_F_MIPMAP)
1411 {
1412 g_string_append_printf(s, "mipmap image #%d mip %d", e->imgid, e->mip);
1413 if(e->img_filename[0]) g_string_append_printf(s, " (%s)", e->img_filename);
1414 }
1415 else if(e->facets & DT_SV_F_THUMB)
1416 {
1417 g_string_append_printf(s, "thumbnail for image #%d (%dx%d, mip %d) %s", e->imgid, e->roi_w,
1418 e->roi_h, e->mip, e->thumb_success ? "ready" : "pending");
1419 }
1420 else if(e->facets & DT_SV_F_BACKBUF)
1421 {
1422 g_string_append_printf(s, "backbuffer 0x%016" G_GINT64_MODIFIER "x", hash);
1423 if(strcmp(module, "?") != 0) g_string_append_printf(s, " (%s)", module);
1424 g_string_append_printf(s, " published by %s pipe (%dx%dx%d, %s)", pipe ? pipe : "?", e->bb_w,
1425 e->bb_h, e->bb_bpp, dev);
1426 _describe_edge(s, "; params", e->param_hash);
1427 }
1428 else if(e->facets & DT_SV_F_CACHE)
1429 {
1430 g_string_append_printf(s, "cacheline 0x%016" G_GINT64_MODIFIER "x", hash);
1431 if(strcmp(module, "?") != 0) g_string_append_printf(s, " (%s", module);
1432 g_string_append_printf(s, ", %s, %dx%d, %s)", pipe ? pipe : "?", e->roi_w, e->roi_h, dev);
1433 _describe_edge(s, " computed from input", e->input_hash);
1434 _describe_edge(s, "; params", e->param_hash);
1435 }
1436 else if(e->facets & DT_SV_F_NODE)
1437 {
1438 g_string_append_printf(s, "node 0x%016" G_GINT64_MODIFIER "x (%s, %s pipe, iop_order %d)", hash,
1439 module, pipe ? pipe : "?", e->iop_order);
1440 }
1441 else if(e->facets & DT_SV_F_HISTORY)
1442 {
1443 g_string_append_printf(s, "history state #%d: %s (%s)", e->history_index, module,
1444 e->enabled ? "enabled" : "disabled");
1445 }
1446 else
1447 {
1448 g_string_append_printf(s, "0x%016" G_GINT64_MODIFIER "x", hash);
1449 }
1450
1451 if(!e->alive) g_string_append(s, " [DELETED]");
1453
1454 return g_string_free(s, FALSE);
1455}
1456
1458{
1460 *c = *src; // copies scalars and the fixed char arrays
1461 c->json = g_strdup(src->json);
1462 c->links = g_array_new(FALSE, FALSE, sizeof(dt_sv_link_t));
1463 if(src->links && src->links->len)
1464 g_array_append_vals(c->links, src->links->data, src->links->len);
1465 return c;
1466}
1467
1469{
1470 GPtrArray *out = g_ptr_array_new_with_free_func(_logged_event_free);
1471 if(!_sv.inited) return out;
1472
1474 for(GList *l = _sv.log ? _sv.log->head : NULL; l; l = l->next)
1475 g_ptr_array_add(out, _event_copy((const dt_sv_logged_event_t *)l->data));
1477 return out;
1478}
1479
1480GPtrArray *dt_supervisor_events_snapshot_since(const uint64_t after_seq, uint64_t *out_last_seq)
1481{
1482 GPtrArray *out = g_ptr_array_new_with_free_func(_logged_event_free);
1483 if(out_last_seq) *out_last_seq = after_seq;
1484 if(!_sv.inited) return out;
1485
1487 // New events are at the tail; walk back until we reach a seq we already have.
1488 for(GList *l = _sv.log ? _sv.log->tail : NULL; l; l = l->prev)
1489 {
1490 const dt_sv_logged_event_t *ev = (const dt_sv_logged_event_t *)l->data;
1491 if(ev->seq <= after_seq) break;
1492 g_ptr_array_add(out, _event_copy(ev));
1493 }
1495
1496 // collected newest-first: reverse to oldest-first
1497 for(guint i = 0, j = out->len ? out->len - 1 : 0; i < j; i++, j--)
1498 {
1499 gpointer t = out->pdata[i];
1500 out->pdata[i] = out->pdata[j];
1501 out->pdata[j] = t;
1502 }
1503 if(out_last_seq && out->len)
1504 *out_last_seq = ((const dt_sv_logged_event_t *)g_ptr_array_index(out, out->len - 1))->seq;
1505 return out;
1506}
1507
1509{
1510 if(!_sv.inited) return 0;
1512 const guint n = _sv.log ? g_queue_get_length(_sv.log) : 0;
1514 return n;
1515}
1516
1517void dt_supervisor_events_free(GPtrArray *events)
1518{
1519 if(events) g_ptr_array_free(events, TRUE);
1520}
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
const dt_develop_name_value_t dt_develop_invert_mask_names[]
Definition blend_gui.c:160
const dt_develop_name_value_t dt_develop_mask_mode_names[]
Definition blend_gui.c:137
@ DEVELOP_COMBINE_INV
Definition blend.h:125
@ DEVELOP_COMBINE_INCL
Definition blend.h:127
const dt_develop_name_value_t dt_develop_blend_colorspace_names[]
Definition blend_gui.c:129
const dt_develop_name_value_t dt_develop_blend_mode_flag_names[]
Definition blend_gui.c:124
@ DEVELOP_BLEND_MODE_MASK
Definition blend.h:109
@ DEVELOP_BLEND_REVERSE
Definition blend.h:108
const dt_develop_name_value_t dt_develop_combine_masks_names[]
Definition blend_gui.c:146
const dt_develop_name_value_t dt_develop_blend_mode_names[]
Definition blend_gui.c:82
const dt_develop_name_value_t dt_develop_feathering_guide_names[]
Definition blend_gui.c:153
@ DEVELOP_MASK_DISABLED
Definition blend.h:114
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
const dt_aligned_pixel_t f
const dt_colormatrix_t dt_aligned_pixel_t out
char * key
int type
char * name
darktable_t darktable
Definition darktable.c:183
#define DT_DEBUG_SUPERVISOR
Definition darktable.h:764
static const dt_aligned_pixel_simd_t value
Definition darktable.h:589
static double dt_get_wtime(void)
Definition darktable.h:966
static int dt_pthread_mutex_unlock(dt_pthread_mutex_t *mutex) RELEASE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:384
static int dt_pthread_mutex_init(dt_pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
Definition dtpthread.h:369
static int dt_pthread_mutex_destroy(dt_pthread_mutex_t *mutex)
Definition dtpthread.h:389
static int dt_pthread_mutex_lock(dt_pthread_mutex_t *mutex) ACQUIRE(mutex) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:374
int bpp
@ DT_INTROSPECTION_TYPE_BOOL
@ DT_INTROSPECTION_TYPE_DOUBLE
@ DT_INTROSPECTION_TYPE_ENUM
@ DT_INTROSPECTION_TYPE_ARRAY
@ DT_INTROSPECTION_TYPE_CHAR
@ DT_INTROSPECTION_TYPE_UINT8
@ DT_INTROSPECTION_TYPE_FLOAT
@ DT_INTROSPECTION_TYPE_SHORT
@ DT_INTROSPECTION_TYPE_UNION
@ DT_INTROSPECTION_TYPE_UINT
@ DT_INTROSPECTION_TYPE_USHORT
@ DT_INTROSPECTION_TYPE_INT8
@ DT_INTROSPECTION_TYPE_STRUCT
@ DT_INTROSPECTION_TYPE_INT
const int t
float *const restrict const size_t k
@ DT_MASKS_POLYGON
Definition masks.h:133
@ DT_MASKS_BRUSH
Definition masks.h:138
@ DT_MASKS_ELLIPSE
Definition masks.h:137
@ DT_MASKS_GRADIENT
Definition masks.h:136
@ DT_MASKS_CIRCLE
Definition masks.h:132
@ DT_MASKS_GROUP
Definition masks.h:134
size_t size
Definition mipmap_cache.c:3
dt_mipmap_buffer_dsc_flags flags
Definition mipmap_cache.c:4
dt_dev_pixelpipe_type_t
Definition pixelpipe.h:36
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
char * dt_pixelpipe_get_pipe_name(dt_dev_pixelpipe_type_t pipe_type)
unsigned __int64 uint64_t
Definition strptime.c:75
int32_t unmuted
Definition darktable.h:778
double start_wtime
Definition darktable.h:846
uint32_t feathering_guide
Definition blend.h:221
gboolean raster_mask_invert
Definition blend.h:238
float exif_exposure
Definition image.h:285
int32_t height
Definition image.h:315
int32_t group_id
Definition image.h:319
char camera_makermodel[128]
Definition image.h:300
float exif_iso
Definition image.h:288
dt_image_loader_t loader
Definition image.h:335
float exif_aperture
Definition image.h:287
int32_t version
Definition image.h:319
int32_t flags
Definition image.h:319
dt_image_orientation_t orientation
Definition image.h:284
int32_t width
Definition image.h:315
float exif_focal_length
Definition image.h:289
char exif_lens[128]
Definition image.h:294
int32_t film_id
Definition image.h:319
int32_t p_height
Definition image.h:315
int32_t p_width
Definition image.h:315
char datetime[200]
Definition image.h:310
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
char folder[PATH_MAX]
Definition image.h:308
int32_t id
Definition image.h:319
dt_introspection_field_t * field
union dt_introspection_field_t * field
dt_introspection_type_t type
dt_introspection_type_enum_tuple_t * values
dt_introspection_type_t type
union dt_introspection_field_t ** fields
gboolean have_introspection
Definition imageop.h:407
dt_masks_type_t type
Definition masks.h:380
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
Definition supervisor.c:54
uint64_t hash
Definition supervisor.c:55
gboolean enabled
Definition supervisor.c:77
char cl_name[64]
Definition supervisor.c:82
int formid
Definition supervisor.c:98
gboolean request_emitted
Definition supervisor.c:106
uint64_t node_hash
Definition supervisor.c:62
uint64_t param_hash
Definition supervisor.c:60
char owner_tag[48]
Definition supervisor.c:105
int32_t imgid
Definition supervisor.c:74
int multi_priority
Definition supervisor.c:70
char widget_tag[48]
Definition supervisor.c:88
char op_name[32]
Definition supervisor.c:68
int owner_pipe_id
Definition supervisor.c:81
guint facets
Definition supervisor.c:56
gboolean thumb_success
Definition supervisor.c:92
uint64_t request_id
Definition supervisor.c:104
int bb_w
Definition supervisor.c:85
uint64_t image_hash
Definition supervisor.c:64
size_t size
Definition supervisor.c:80
uint64_t input_hash
Definition supervisor.c:61
int devid
Definition supervisor.c:76
int mip
Definition supervisor.c:91
int roi_w
Definition supervisor.c:75
uint64_t pred_hash
Definition supervisor.c:65
int history_index
Definition supervisor.c:72
int pipe_type
Definition supervisor.c:73
gboolean alive
Definition supervisor.c:57
char form_name[64]
Definition supervisor.c:100
int roi_h
Definition supervisor.c:75
uint64_t history_hash
Definition supervisor.c:63
int bb_h
Definition supervisor.c:85
uint64_t awaits_hash
Definition supervisor.c:103
int iop_order
Definition supervisor.c:71
char multi_name[64]
Definition supervisor.c:69
int bb_bpp
Definition supervisor.c:85
char img_filename[128]
Definition supervisor.c:95
int form_type
Definition supervisor.c:99
static JsonArray * _forms_json(GList *forms)
Definition supervisor.c:247
uint64_t dt_supervisor_form_key(const int formid)
Definition supervisor.c:209
void dt_supervisor_events_free(GPtrArray *events)
static int _entry_pipe_type(const dt_sv_entry_t *e)
Definition supervisor.c:376
gchar * dt_supervisor_describe(const uint64_t hash)
void dt_supervisor_cache_wait(const dt_sv_op_t op, const uint64_t request_id, const uint64_t awaited_hash, const char *owner_tag, const char *op_name, const int multi_priority, const int pipe_type, const int32_t imgid, const gboolean request_emitted, const char *note)
static uint64_t _form_key(const int formid)
Definition supervisor.c:196
void dt_supervisor_set_recording(const gboolean on)
Definition supervisor.c:305
gint dt_supervisor_recording
Definition supervisor.c:112
uint64_t next_seq
Definition supervisor.c:118
static void _introspect_dump(dt_introspection_field_t *field, const char *prefix, gpointer params, JsonArray *out)
Definition supervisor.c:593
static uint64_t _fnv1a(const char *s, const int a, const int b)
Definition supervisor.c:150
void dt_supervisor_events_clear(void)
Definition supervisor.c:310
static uint64_t _image_key(const int32_t imgid)
Definition supervisor.c:186
static void _emit_line(JsonObject *root)
Definition supervisor.c:566
static uint64_t _cache_wait_key(const uint64_t request_id)
Definition supervisor.c:204
static uint64_t _mipmap_key(const int32_t imgid, const int mip)
Definition supervisor.c:176
static uint64_t _parse_hash(const char *s)
Definition supervisor.c:423
static gboolean _touch_alive_locked(dt_sv_entry_t *e, const gboolean created, const dt_sv_op_t op)
Definition supervisor.c:346
void dt_supervisor_mipmap(const dt_sv_op_t op, const int32_t imgid, const int mip)
static void _describe_edge(GString *s, const char *prefix, const uint64_t hash)
void dt_supervisor_cacheline_delete(const uint64_t hash, const size_t size, const int owner_pipe_id, const char *name)
Definition supervisor.c:982
void dt_supervisor_init(void)
Definition supervisor.c:283
GPtrArray * dt_supervisor_events_snapshot(void)
void dt_supervisor_rekey(const uint64_t old_hash, const uint64_t new_hash)
static struct @36 _sv
gboolean inited
Definition supervisor.c:120
GHashTable * entries
Definition supervisor.c:116
static void _arr_addf(JsonArray *out, const char *fmt,...) G_GNUC_PRINTF(2
Definition supervisor.c:728
void dt_supervisor_cacheline_read(const uint64_t hash, const size_t size)
Definition supervisor.c:952
static dt_sv_entry_t * _entry_get_locked(const uint64_t hash, gboolean *created)
Definition supervisor.c:321
static void _module_label(const dt_sv_entry_t *e, char *out, const size_t out_size)
Definition supervisor.c:384
void dt_supervisor_thumbnail(const dt_sv_op_t op, const int32_t imgid, const int width, const int height, const int mip, const gboolean success)
GPtrArray * dt_supervisor_events_snapshot_since(const uint64_t after_seq, uint64_t *out_last_seq)
static JsonArray * _params_json(const dt_iop_module_t *module, const void *params)
Definition supervisor.c:711
uint64_t dt_supervisor_node_key(const int pipe_type, const char *op_name, const int multi_priority)
Definition supervisor.c:161
static const char * _op_str(const dt_sv_op_t op)
Definition supervisor.c:137
uint64_t dt_supervisor_image_key(const int32_t imgid)
Definition supervisor.c:191
void dt_supervisor_cleanup(void)
Definition supervisor.c:292
void dt_supervisor_image(const dt_sv_op_t op, const int32_t imgid, const dt_image_t *img)
static dt_sv_logged_event_t * _extract_event(JsonObject *root, const gchar *json_str)
Definition supervisor.c:430
static void _logged_event_free(gpointer p)
Definition supervisor.c:123
void dt_supervisor_form(const dt_sv_op_t op, const dt_masks_form_t *form)
dt_pthread_mutex_t lock
Definition supervisor.c:119
static JsonObject * _resolve_locked(const uint64_t hash)
Definition supervisor.c:400
static const char * _form_type_name(const int type)
Definition supervisor.c:214
GQueue * log
Definition supervisor.c:117
static JsonArray * _blendop_json(const dt_develop_blend_params_t *bp)
Definition supervisor.c:755
void dt_supervisor_widget(const dt_sv_op_t op, const char *widget_tag, const uint64_t consumed_hash, const int pipe_type, const int32_t imgid)
static const char * _primary_domain(const dt_sv_entry_t *e)
Definition supervisor.c:360
static void _device_string(const int devid, char *out, const size_t out_size)
Definition supervisor.c:270
uint64_t dt_supervisor_thumbnail_key(const int32_t imgid, const int mip)
Definition supervisor.c:171
static dt_sv_logged_event_t * _event_copy(const dt_sv_logged_event_t *src)
static void _set_hash_member(JsonObject *o, const char *name, const uint64_t hash)
Definition supervisor.c:390
guint dt_supervisor_events_count(void)
uint64_t dt_supervisor_mipmap_key(const int32_t imgid, const int mip)
Definition supervisor.c:181
void dt_supervisor_cacheline_create(const uint64_t hash, const uint64_t node_hash, const uint64_t param_hash, const uint64_t input_hash, const char *op_name, const int multi_priority, const int iop_order, const int pipe_type, const int32_t imgid, const int roi_w, const int roi_h, const int devid, const size_t size, const char *name)
Definition supervisor.c:891
void dt_supervisor_node(const dt_sv_op_t op, const uint64_t node_hash, const uint64_t param_hash, const uint64_t predecessor_hash, const char *op_name, const int multi_priority, const int iop_order, const int pipe_type, const int32_t imgid)
Definition supervisor.c:856
#define DT_SV_LOG_MAX
Definition supervisor.c:110
static JsonObject * _envelope(const dt_sv_op_t op, const char *domain, const uint64_t hash, const int pipe_type, const int32_t imgid, const gboolean alive, const gboolean resurrected)
Definition supervisor.c:786
static JsonObject * _rekey_record(const dt_sv_op_t op, const dt_sv_entry_t *e, const uint64_t hash, const char *link_field, const uint64_t link_hash, const gboolean alive)
static const char * _thread_tag(void)
Definition supervisor.c:276
static const char * _name_for_value(const dt_develop_name_value_t *list, const int value)
Definition supervisor.c:738
dt_sv_facet_t
Definition supervisor.c:36
@ DT_SV_F_MIPMAP
Definition supervisor.c:43
@ DT_SV_F_NODE
Definition supervisor.c:38
@ DT_SV_F_THUMB
Definition supervisor.c:42
@ DT_SV_F_WIDGET
Definition supervisor.c:41
@ DT_SV_F_FORM
Definition supervisor.c:45
@ DT_SV_F_CACHE
Definition supervisor.c:39
@ DT_SV_F_HISTORY
Definition supervisor.c:37
@ DT_SV_F_CACHEWAIT
Definition supervisor.c:46
@ DT_SV_F_IMAGE
Definition supervisor.c:44
@ DT_SV_F_BACKBUF
Definition supervisor.c:40
static uint64_t _thumb_key(const int32_t imgid, const int mip)
Definition supervisor.c:166
static gboolean _hash_is_set(const uint64_t h)
Definition supervisor.c:132
void dt_supervisor_backbuf(const dt_sv_op_t op, const uint64_t hash, const uint64_t history_hash, const int w, const int h, const int bpp, const int pipe_type, const int devid)
static void _log_push(dt_sv_logged_event_t *e)
Definition supervisor.c:549
static JsonObject * _image_properties_json(const dt_image_t *img)
static JsonArray * _form_members_json(const dt_masks_form_t *form)
Definition supervisor.c:228
static void _arr_add_enum(JsonArray *out, const char *label, const dt_develop_name_value_t *names, const int value)
Definition supervisor.c:745
void dt_supervisor_history(const dt_sv_op_t op, const uint64_t param_hash, const char *op_name, const int multi_priority, const char *multi_name, const int iop_order, const int history_index, const int32_t imgid, const gboolean enabled, const dt_iop_module_t *module, const void *params, const dt_develop_blend_params_t *blend_params, GList *forms)
Definition supervisor.c:804
dt_sv_op_t
Definition supervisor.h:76
@ DT_SV_READ
Definition supervisor.h:79
@ DT_SV_UPDATE
Definition supervisor.h:78
@ DT_SV_CREATE
Definition supervisor.h:77
@ DT_SV_DELETE
Definition supervisor.h:80
static gboolean dt_supervisor_active(void)
Definition supervisor.h:94
dt_introspection_type_header_t header
dt_introspection_type_array_t Array
dt_introspection_type_enum_t Enum
dt_introspection_type_struct_t Struct