Project
Loading...
Searching...
No Matches
FrameworkGUIDevicesGraph.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
13#include "FrameworkGUIState.h"
14#include "PaletteHelpers.h"
18#include "Framework/Logger.h"
20#include "Framework/Logger.h"
21#include "../src/WorkflowHelpers.h"
22#include "DebugGUI/imgui.h"
23#include <DebugGUI/icons_font_awesome.h>
24#include <algorithm>
25#include <cmath>
26#include <fstream>
27#include <iomanip>
28#include <sstream>
29#include <string>
30#include <string_view>
31#include <vector>
32
33#pragma GCC diagnostic push
34#pragma GCC diagnostic ignored "-Wpedantic"
35static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); }
36static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); }
37static inline ImVec2 operator*(const ImVec2& lhs, float rhs) { return ImVec2(lhs.x * rhs, lhs.y * rhs); }
38static inline ImVec2 operator/(const ImVec2& lhs, float rhs) { return ImVec2(lhs.x / rhs, lhs.y / rhs); }
39
40namespace o2::framework::gui
41{
42struct NodeColor {
43 ImVec4 normal;
44 ImVec4 hovered;
45 ImVec4 title;
46 ImVec4 title_hovered;
47};
48
50
51NodeColor decideColorForNode(const DeviceInfo& info, bool lightMode)
52{
53 if (lightMode) {
54 // Dark-on-bright: rich medium-dark cards on a white canvas, white text on nodes
55 if (info.active == false) {
56 return NodeColor{
57 .normal = ImVec4(0xb5 / 255.f, 0x26 / 255.f, 0x18 / 255.f, 1), // dark crimson
58 .hovered = ImVec4(0xc2 / 255.f, 0x2d / 255.f, 0x1d / 255.f, 1)};
59 }
60 switch (info.streamingState) {
62 return NodeColor{
63 .normal = ImVec4(0x8c / 255.f, 0x6c / 255.f, 0x00 / 255.f, 1), // dark amber
64 .hovered = ImVec4(0x9e / 255.f, 0x7a / 255.f, 0x00 / 255.f, 1),
65 .title = ImVec4(0x6e / 255.f, 0x54 / 255.f, 0x00 / 255.f, 1),
66 .title_hovered = ImVec4(0x5a / 255.f, 0x44 / 255.f, 0x00 / 255.f, 1)};
68 return NodeColor{
69 .normal = ImVec4(0x1a / 255.f, 0x80 / 255.f, 0x40 / 255.f, 1), // dark forest green
70 .hovered = ImVec4(0x22 / 255.f, 0x8b / 255.f, 0x47 / 255.f, 1),
71 .title = ImVec4(0x11 / 255.f, 0x60 / 255.f, 0x2e / 255.f, 1),
72 .title_hovered = ImVec4(0x0a / 255.f, 0x4d / 255.f, 0x23 / 255.f, 1)};
74 default:
75 return NodeColor{
76 .normal = ImVec4(0x3a / 255.f, 0x3a / 255.f, 0x3c / 255.f, 1), // macOS tertiary dark
77 .hovered = ImVec4(0x48 / 255.f, 0x48 / 255.f, 0x4a / 255.f, 1),
78 .title = ImVec4(0x2c / 255.f, 0x2c / 255.f, 0x2e / 255.f, 1),
79 .title_hovered = ImVec4(0x1c / 255.f, 0x1c / 255.f, 0x1e / 255.f, 1)};
80 }
81 }
82 if (info.active == false) {
83 return NodeColor{
84 .normal = PaletteHelpers::RED,
86 }
87 switch (info.streamingState) {
89 return NodeColor{
91 .hovered = PaletteHelpers::YELLOW,
93 .title_hovered = PaletteHelpers::DARK_YELLOW};
95 return NodeColor{
97 .hovered = PaletteHelpers::GREEN,
98 .title = PaletteHelpers::GREEN,
99 .title_hovered = PaletteHelpers::DARK_GREEN};
101 default:
102 return NodeColor{
103 .normal = PaletteHelpers::GRAY,
105 .title = PaletteHelpers::GRAY,
106 .title_hovered = PaletteHelpers::BLACK};
107 }
108}
109
111const static ImColor INPUT_SLOT_COLOR = {150, 150, 150, 150};
112const static ImColor OUTPUT_SLOT_COLOR = {150, 150, 150, 150};
113const static ImColor NODE_LABEL_BACKGROUND_COLOR = {45, 45, 45, 255};
114const static ImColor NODE_LABEL_TEXT_COLOR = {244, 244, 244, 255};
115const static ImVec4& ERROR_MESSAGE_COLOR = PaletteHelpers::RED;
116const static ImVec4& WARNING_MESSAGE_COLOR = PaletteHelpers::YELLOW;
117const static ImColor ARROW_BACKGROUND_COLOR = {100, 100, 0};
118const static ImColor ARROW_HALFGROUND_COLOR = {170, 170, 70};
119const static ImColor ARROW_COLOR = {200, 200, 100};
120const static ImColor ARROW_SELECTED_COLOR = {200, 0, 100};
121const static ImU32 GRID_COLOR = ImColor(150, 150, 150, 80);
122const static ImColor NODE_BORDER_COLOR = {100, 100, 100};
123const static ImColor LEGEND_COLOR = {100, 100, 100};
124
126const static float GRID_SZ = 64.0f;
127const static float ARROW_BACKGROUND_THICKNESS = 1.f;
128const static float ARROW_HALFGROUND_THICKNESS = 2.f;
129const static float ARROW_THICKNESS = 3.f;
130const static float NODE_BORDER_THICKNESS = 4.f;
131const static ImVec4 LEGEND_BACKGROUND_COLOR = {0.125, 0.180, 0.196, 1};
132
133const static ImVec4 SLOT_EMPTY_COLOR = {0.275, 0.275, 0.275, 1.};
134const static ImVec4& SLOT_PENDING_COLOR = PaletteHelpers::RED;
135const static ImVec4& SLOT_DISPATCHED_COLOR = PaletteHelpers::YELLOW;
136const static ImVec4& SLOT_DONE_COLOR = PaletteHelpers::GREEN;
137
139const float NODE_SLOT_RADIUS = 4.0f;
140const ImVec2 NODE_WINDOW_PADDING(8.0f, 8.0f);
141
143float clampZoom(float zoom)
144{
145 return std::clamp(zoom, 0.15f, 2.50f);
146}
147
148ImVec2 graphToScreen(ImVec2 graphPos, ImVec2 canvasOrigin, ImVec2 scrolling, float zoom)
149{
150 return canvasOrigin + (graphPos - scrolling) * zoom;
151}
152
153void displayGrid(bool show_grid, ImVec2 scrolling, float zoom, ImDrawList* draw_list)
154{
155 if (show_grid == false) {
156 return;
157 }
158 ImVec2 win_pos = ImGui::GetCursorScreenPos();
159 ImVec2 canvas_sz = ImGui::GetWindowSize();
160 float gridSize = GRID_SZ * zoom;
161 for (float x = fmodf(-scrolling.x * zoom, gridSize); x < canvas_sz.x; x += gridSize) {
162 draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, GRID_COLOR);
163 }
164 for (float y = fmodf(-scrolling.y * zoom, gridSize); y < canvas_sz.y; y += gridSize) {
165 draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, GRID_COLOR);
166 }
167}
168
169void displayLegend(bool show_legend, ImVec2 offset, ImDrawList* draw_list)
170{
171 if (show_legend == false) {
172 return;
173 }
174 struct LegendItem {
175 std::string label;
176 const ImVec4& color;
177 };
178 static auto legend = {
179 LegendItem{" Slot empty", SLOT_EMPTY_COLOR},
180 LegendItem{" Slot pending", SLOT_PENDING_COLOR},
181 LegendItem{" Slot dispatched", SLOT_DISPATCHED_COLOR},
182 LegendItem{" Slot done", SLOT_DONE_COLOR},
183 };
184 ImGui::PushStyleColor(ImGuiCol_WindowBg, LEGEND_BACKGROUND_COLOR);
185 ImGui::PushStyleColor(ImGuiCol_TitleBg, LEGEND_BACKGROUND_COLOR);
186 ImGui::PushStyleColor(ImGuiCol_ResizeGrip, 0);
187
188 ImGui::Begin("Legend");
189 ImGui::Dummy(ImVec2(0.0f, 10.0f));
190 for (auto [label, color] : legend) {
191 ImVec2 vMin = ImGui::GetWindowPos() + ImGui::GetCursorPos() + ImVec2(9, 0);
192 ImVec2 vMax = vMin + ImGui::CalcTextSize(" ");
193 ImGui::PushStyleColor(ImGuiCol_ChildBg, color);
194 ImGui::GetWindowDrawList()->AddRectFilled(vMin, vMax, ImColor(color));
195 ImGui::GetWindowDrawList()->AddRect(vMin, vMax, GRID_COLOR);
196 ImGui::Text("%s", label.data());
197 ImGui::PopStyleColor();
198 }
199 ImGui::End();
200 ImGui::PopStyleColor(3);
201}
202
203#define MAX_GROUP_NAME_SIZE 128
204
205// Private helper struct to keep track of node groups
206struct Group {
207 int ID;
209 size_t metadataId;
210 Group(int id, char const* n, size_t mid)
211 {
212 ID = id;
213 strncpy(name, n, MAX_GROUP_NAME_SIZE);
214 name[MAX_GROUP_NAME_SIZE - 1] = 0;
215 metadataId = mid;
216 }
217};
218
219constexpr int MAX_SLOTS = 512;
220constexpr int MAX_INPUT_VALUE_SIZE = 24;
221
222struct OldestPossibleInput {
223 size_t value;
224 char buffer[MAX_INPUT_VALUE_SIZE] = "unknown";
225 float textSize = ImGui::CalcTextSize("unknown").x;
226};
227
228struct OldestPossibleOutput {
229 size_t value;
230 char buffer[MAX_INPUT_VALUE_SIZE] = "unknown";
231 float textSize = ImGui::CalcTextSize("unknown").x;
232};
233
234// Private helper struct for the graph model
235struct Node {
236 int ID;
237 int GroupID;
238 char Name[64];
239 ImVec2 Size;
240 float Value;
241 ImVec4 Color;
242 int InputsCount, OutputsCount;
243 OldestPossibleInput oldestPossibleInput[MAX_SLOTS];
244 OldestPossibleOutput oldestPossibleOutput[MAX_SLOTS];
245
246 Node(int id, int groupID, char const* name, float value, const ImVec4& color, int inputs_count, int outputs_count)
247 {
248 ID = id;
249 GroupID = groupID;
250 strncpy(Name, name, 63);
251 Name[63] = 0;
252 Size = ImVec2(150.f, 128.f);
253 Value = value;
254 Color = color;
255 InputsCount = inputs_count;
256 OutputsCount = outputs_count;
257 }
258};
259
260// Private helper struct for the layout of the graph
261struct NodePos {
262 ImVec2 pos;
263 static ImVec2 GetInputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
264 {
265 ImVec2 const& pos = positions[nodeId].pos;
266 ImVec2 const& size = infos[nodeId].Size;
267 float inputsCount = infos[nodeId].InputsCount;
268 return ImVec2(pos.x, pos.y + size.y * ((float)slot_no + 1) / (inputsCount + 1));
269 }
270 static ImVec2 GetOutputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
271 {
272 ImVec2 const& pos = positions[nodeId].pos;
273 ImVec2 const& size = infos[nodeId].Size;
274 float outputsCount = infos[nodeId].OutputsCount;
275 return ImVec2(pos.x + size.x, pos.y + size.y * ((float)slot_no + 1) / (outputsCount + 1));
276 }
277};
278
279// Private helper struct for the edges in the graph
280struct NodeLink {
281 int InputIdx, InputSlot, OutputIdx, OutputSlot;
282
283 NodeLink(int input_idx, int input_slot, int output_idx, int output_slot)
284 {
285 InputIdx = input_idx;
286 InputSlot = input_slot;
287 OutputIdx = output_idx;
288 OutputSlot = output_slot;
289 }
290};
291
292std::string xmlEscape(const char* text)
293{
294 std::string result;
295 for (char c : std::string_view{text}) {
296 switch (c) {
297 case '&':
298 result += "&amp;";
299 break;
300 case '<':
301 result += "&lt;";
302 break;
303 case '>':
304 result += "&gt;";
305 break;
306 case '"':
307 result += "&quot;";
308 break;
309 case '\'':
310 result += "&apos;";
311 break;
312 default:
313 result += c;
314 break;
315 }
316 }
317 return result;
318}
319
320std::string dotEscape(const char* text)
321{
322 std::string result;
323 for (char c : std::string_view{text}) {
324 if (c == '"' || c == '\\') {
325 result += '\\';
326 }
327 result += c;
328 }
329 return result;
330}
331
332std::string colorToHex(ImVec4 color)
333{
334 auto component = [](float value) {
335 return std::clamp(static_cast<int>(std::round(value * 255.f)), 0, 255);
336 };
337 std::ostringstream out;
338 out << "#" << std::hex << std::setfill('0') << std::setw(2) << component(color.x)
339 << std::setw(2) << component(color.y)
340 << std::setw(2) << component(color.z);
341 return out.str();
342}
343
344bool topologyBounds(ImVector<Node> const& nodes, ImVector<NodePos> const& positions, ImVec2& minPos, ImVec2& maxPos)
345{
346 if (nodes.Size == 0 || positions.Size == 0) {
347 return false;
348 }
349 minPos = positions[0].pos;
350 maxPos = positions[0].pos + nodes[0].Size;
351 for (int i = 1; i < nodes.Size; ++i) {
352 minPos.x = std::min(minPos.x, positions[i].pos.x);
353 minPos.y = std::min(minPos.y, positions[i].pos.y);
354 maxPos.x = std::max(maxPos.x, positions[i].pos.x + nodes[i].Size.x);
355 maxPos.y = std::max(maxPos.y, positions[i].pos.y + nodes[i].Size.y);
356 }
357 return true;
358}
359
360bool exportTopologySVG(const char* filename,
361 ImVector<Node> const& nodes,
362 ImVector<NodePos> const& positions,
363 ImVector<NodeLink> const& links,
364 std::vector<DeviceInfo> const& infos,
365 bool lightMode)
366{
367 ImVec2 minPos;
368 ImVec2 maxPos;
369 if (!topologyBounds(nodes, positions, minPos, maxPos)) {
370 return false;
371 }
372 constexpr float MARGIN = 80.f;
373 auto toSVG = [minPos](ImVec2 p) {
374 return p - minPos + ImVec2(80.f, 80.f);
375 };
376 ImVec2 canvasSize = maxPos - minPos + ImVec2(2.f * MARGIN, 2.f * MARGIN);
377
378 std::ofstream out(filename);
379 if (!out.is_open()) {
380 return false;
381 }
382 out << std::fixed << std::setprecision(2);
383 out << R"(<?xml version="1.0" encoding="UTF-8"?>)" << "\n";
384 out << R"(<svg xmlns="http://www.w3.org/2000/svg" width=")" << canvasSize.x << "\" height=\"" << canvasSize.y
385 << "\" viewBox=\"0 0 " << canvasSize.x << " " << canvasSize.y << "\">\n";
386 out << " <defs>\n";
387 out << " <marker id=\"arrow\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto-start-reverse\">\n";
388 out << " <path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"#c8c864\"/>\n";
389 out << " </marker>\n";
390 out << " </defs>\n";
391 out << R"( <rect width="100%" height="100%" fill=")" << (lightMode ? "#fafafc" : "#2c2c2e") << "\"/>\n";
392 out << " <g id=\"links\" fill=\"none\" stroke=\"#c8c864\" stroke-width=\"3\" marker-end=\"url(#arrow)\">\n";
393 for (int i = 0; i < links.Size; ++i) {
394 auto const& link = links[i];
395 ImVec2 p1 = toSVG(NodePos::GetOutputSlotPos(nodes, positions, link.InputIdx, link.InputSlot));
396 ImVec2 p2 = toSVG(NodePos::GetInputSlotPos(nodes, positions, link.OutputIdx, link.OutputSlot) + ImVec2(-3 * NODE_SLOT_RADIUS, 0));
397 out << " <path id=\"edge-" << i << "\" d=\"M " << p1.x << " " << p1.y
398 << " C " << p1.x + 50.f << " " << p1.y << ", " << p2.x - 50.f << " " << p2.y
399 << ", " << p2.x << " " << p2.y << "\"/>\n";
400 }
401 out << " </g>\n";
402 out << " <g id=\"nodes\" font-family=\"sans-serif\" font-size=\"13\" fill=\"#f4f4f4\">\n";
403 for (int i = 0; i < nodes.Size; ++i) {
404 auto const& node = nodes[i];
405 if (i >= infos.size()) {
406 continue;
407 }
408 auto const& info = infos[i];
409 auto colors = decideColorForNode(info, lightMode);
410 auto titleColor = colors.title.w == 0.f ? colors.normal : colors.title;
411 ImVec2 p = toSVG(positions[i].pos);
412 out << " <g id=\"node-" << i << "\" data-label=\"" << xmlEscape(node.Name) << "\">\n";
413 out << " <rect x=\"" << p.x + 3.f << "\" y=\"" << p.y + 3.f << "\" width=\"" << node.Size.x
414 << "\" height=\"" << node.Size.y << "\" rx=\"4\" fill=\"#000000\" opacity=\"0.28\"/>\n";
415 out << " <rect x=\"" << p.x << "\" y=\"" << p.y << "\" width=\"" << node.Size.x
416 << "\" height=\"" << node.Size.y << R"(" rx="4" fill=")" << colorToHex(colors.normal)
417 << "\" stroke=\"#646464\" stroke-width=\"4\"/>\n";
418 out << " <rect x=\"" << p.x << "\" y=\"" << p.y << "\" width=\"" << node.Size.x
419 << R"(" height="24" rx="4" fill=")" << colorToHex(titleColor) << "\"/>\n";
420 out << " <text x=\"" << p.x + NODE_WINDOW_PADDING.x << "\" y=\"" << p.y + 17.f << "\">"
421 << xmlEscape(node.Name) << "</text>\n";
422 for (int slot = 0; slot < node.InputsCount; ++slot) {
423 ImVec2 slotPos = toSVG(NodePos::GetInputSlotPos(nodes, positions, i, slot));
424 out << " <circle cx=\"" << slotPos.x << "\" cy=\"" << slotPos.y << "\" r=\"" << NODE_SLOT_RADIUS << "\" fill=\"#969696\" opacity=\"0.6\"/>\n";
425 }
426 for (int slot = 0; slot < node.OutputsCount; ++slot) {
427 ImVec2 slotPos = toSVG(NodePos::GetOutputSlotPos(nodes, positions, i, slot));
428 out << " <circle cx=\"" << slotPos.x << "\" cy=\"" << slotPos.y << "\" r=\"" << NODE_SLOT_RADIUS << "\" fill=\"#969696\" opacity=\"0.6\"/>\n";
429 }
430 out << " </g>\n";
431 }
432 out << " </g>\n";
433 out << "</svg>\n";
434 return true;
435}
436
437bool exportTopologyDOT(const char* filename,
438 ImVector<Node> const& nodes,
439 ImVector<NodePos> const& positions,
440 ImVector<NodeLink> const& links,
441 std::vector<DeviceInfo> const& infos,
442 bool lightMode)
443{
444 std::ofstream out(filename);
445 if (!out.is_open()) {
446 return false;
447 }
448 out << "digraph dpl_topology {\n";
449 out << " graph [rankdir=LR, splines=curved, outputorder=edgesfirst];\n";
450 out << " node [shape=box, style=\"rounded,filled\", fontname=\"Helvetica\", fontsize=10];\n";
451 out << " edge [color=\"#c8c864\"];\n";
452 for (int i = 0; i < nodes.Size; ++i) {
453 if (i >= infos.size()) {
454 continue;
455 }
456 auto colors = decideColorForNode(infos[i], lightMode);
457 out << " n" << i << " [label=\"" << dotEscape(nodes[i].Name)
458 << "\", fillcolor=\"" << colorToHex(colors.normal)
459 << R"(", fontcolor="white", pos=")" << positions[i].pos.x / 72.f << "," << -positions[i].pos.y / 72.f << "!\"];\n";
460 }
461 for (int i = 0; i < links.Size; ++i) {
462 out << " n" << links[i].InputIdx << " -> n" << links[i].OutputIdx << ";\n";
463 }
464 out << "}\n";
465 return true;
466}
467
469template <typename RECORD, typename ITEM, typename CONTEXT>
470struct MetricsPainter {
471 using NumRecordsCallback = std::function<size_t(void)>;
472 using RecordCallback = std::function<RECORD(size_t)>;
473 using NumItemsCallback = std::function<size_t(RECORD const&)>;
474 using ItemCallback = std::function<ITEM const&(RECORD const&, size_t)>;
475 using ValueCallback = std::function<int(ITEM const&)>;
476 using ColorCallback = std::function<ImU32(int value)>;
477 using ContextCallback = std::function<CONTEXT&()>;
478 using PaintCallback = std::function<void(int row, int column, int value, ImU32 color, CONTEXT const& context)>;
479
480 static void draw(const char* name,
481 ImVec2 const& offset,
482 ImVec2 const& sizeHint,
483 CONTEXT const& context,
484 NumRecordsCallback const& getNumRecords,
485 RecordCallback const& getRecord,
486 NumItemsCallback const& getNumItems,
487 ItemCallback const& getItem,
488 ValueCallback const& getValue,
489 ColorCallback const& getColor,
490 PaintCallback const& describeCell)
491 {
492 for (size_t ri = 0; ri < getNumRecords(); ++ri) {
493 auto record = getRecord(ri);
494 for (size_t ii = 0; ii < getNumItems(record); ++ii) {
495 auto item = getItem(record, ii);
496 int value = getValue(item);
497 ImU32 color = getColor(value);
498 ImVec2 pos = ImGui::GetCursorScreenPos();
499 describeCell(ri, ii, value, color, context);
500 }
501 }
502 }
503
504 static NumRecordsCallback metric1D()
505 {
506 return []() -> size_t { return 1UL; };
507 }
508
509 static NumRecordsCallback metric2D(Metric2DViewIndex& viewIndex)
510 {
511 return [&viewIndex]() -> size_t {
512 if (viewIndex.isComplete()) {
513 return viewIndex.w;
514 }
515 return 0;
516 };
517 }
518
519 static NumItemsCallback items1D()
520 {
521 return [](RECORD const& record) -> size_t { return 1UL; };
522 }
523
524 static NumItemsCallback items2D(Metric2DViewIndex const& viewIndex)
525 {
526 return [&viewIndex](int record) -> int {
527 if (viewIndex.isComplete()) {
528 return viewIndex.h;
529 }
530 return 0;
531 };
532 }
533
534 template <typename T>
535 static ItemCallback latestMetric(DeviceMetricsInfo const& metrics,
536 Metric2DViewIndex const& viewIndex)
537 {
538 return [&metrics, &viewIndex](RECORD const& record, size_t i) -> ITEM const& {
539 // Calculate the index in the viewIndex.
540 auto idx = record * viewIndex.h + i;
541 assert(viewIndex.indexes.size() > idx);
542 MetricInfo const& metricInfo = metrics.metrics[viewIndex.indexes[idx]];
543 auto& data = DeviceMetricsInfoHelpers::get<T, metricStorageSize<T>()>(metrics, metricInfo.storeIdx);
544 return data[(metricInfo.pos - 1) % data.size()];
545 };
546 }
547
548 template <typename T>
549 static RecordCallback simpleRecord()
550 {
551 return [](size_t i) -> int {
552 return i;
553 };
554 }
555
556 template <typename T>
557 static ValueCallback simpleValue()
558 {
559 return [](ITEM const& item) -> T { return item; };
560 }
561
562 static ColorCallback colorPalette(std::vector<ImU32> const& colors, int minValue, int maxValue)
563 {
564 return [colors, minValue, maxValue](int const& value) -> ImU32 {
565 if (value < minValue) {
566 return colors[0];
567 } else if (value > maxValue) {
568 return colors[colors.size() - 1];
569 } else {
570 int idx = (value - minValue) * (colors.size() - 1) / (maxValue - minValue);
571 return colors[idx];
572 }
573 };
574 }
575};
576
578struct MetricLabelsContext {
579 ImVector<Node>* nodes;
580 int nodeIdx;
581 ImVector<NodePos>* positions;
582 ImDrawList* draw_list;
583 ImVec2 canvasOrigin;
584 ImVec2 scrolling;
585 float zoom;
586};
587
588void showTopologyNodeGraph(WorkspaceGUIState& state,
589 std::vector<DeviceInfo> const& infos,
590 std::vector<DeviceSpec> const& specs,
591 std::vector<DataProcessingStates> const& allStates,
592 std::vector<DataProcessorInfo> const& metadata,
593 std::vector<DeviceControl>& controls,
594 std::vector<DeviceMetricsInfo> const& metricsInfos)
595{
596 ImGui::SetNextWindowPos(ImVec2(0, 0), 0);
597 if (state.bottomPaneVisible) {
598 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y - state.bottomPaneSize), 0);
599 } else {
600 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y), 0);
601 }
602
603 ImGui::Begin("Physical topology view", nullptr, ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
604
605 static ImVector<Node> nodes;
606 static ImVector<Group> groups;
607 static ImVector<NodeLink> links;
608 static ImVector<NodePos> positions;
609
610 static bool inited = false;
611 static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
612 static float zoom = 1.0f;
613 static bool show_grid = true;
614 static bool show_legend = true;
615 static int node_selected = -1;
616 static char exportStatus[256] = "";
617 bool fitRequested = false;
618
619 auto prepareChannelView = [&specs, &metricsInfos, &metadata](ImVector<Node>& nodeList, ImVector<Group>& groupList) {
620 struct LinkInfo {
621 int specId;
622 int outputId;
623 };
624 std::map<std::string, LinkInfo> linkToIndex;
625 for (int si = 0; si < specs.size(); ++si) {
626 int oi = 0;
627 for (auto&& output : specs[si].outputChannels) {
628 linkToIndex.insert(std::make_pair(output.name, LinkInfo{si, oi}));
629 oi += 1;
630 }
631 }
632 // Prepare the list of groups.
633 std::string workflow = "Ungrouped";
634 int groupId = 0;
635 for (size_t mi = 0; mi < metadata.size(); ++mi) {
636 auto const& metadatum = metadata[mi];
637 if (metadatum.executable == workflow) {
638 continue;
639 }
640 workflow = metadatum.executable;
641 char* groupBasename = strrchr(workflow.data(), '/');
642 char const* groupName = groupBasename ? groupBasename + 1 : workflow.data();
643 bool hasDuplicate = false;
644 for (size_t gi = 0; gi < groupList.Size; ++gi) {
645 if (strncmp(groupName, groupList[gi].name, MAX_GROUP_NAME_SIZE - 1) == 0) {
646 hasDuplicate = true;
647 break;
648 }
649 }
650 if (hasDuplicate == false) {
651 groupList.push_back(Group(groupId++, groupName, mi));
652 }
653 }
654 // Do matching between inputs and outputs
655 for (int si = 0; si < specs.size(); ++si) {
656 auto& spec = specs[si];
657 int groupId = 0;
658
659 auto metadatum = std::find_if(metadata.begin(), metadata.end(),
660 [&name = spec.name](DataProcessorInfo const& info) { return info.name == name; });
661
662 for (size_t gi = 0; gi < groupList.Size; ++gi) {
663 if (metadatum == metadata.end()) {
664 break;
665 }
666 const char* groupName = strrchr(metadatum->executable.data(), '/');
667 if (strncmp(groupList[gi].name, groupName ? groupName + 1 : metadatum->executable.data(), 127) == 0) {
668 groupId = gi;
669 break;
670 }
671 }
672 nodeList.push_back(Node(si, groupId, spec.id.c_str(), 0.5f,
673 ImColor(255, 100, 100),
674 spec.inputChannels.size(),
675 spec.outputChannels.size()));
676 int ii = 0;
677 for (auto& input : spec.inputChannels) {
678 auto const& outName = input.name;
679 auto const& out = linkToIndex.find(input.name);
680 if (out == linkToIndex.end()) {
681 LOG(error) << "Could not find suitable node for " << outName;
682 continue;
683 }
684 links.push_back(NodeLink{out->second.specId, out->second.outputId, si, ii});
685 ii += 1;
686 }
687 }
688
689 // ImVector does boudary checks, so I bypass the case there is no
690 // edges.
691 std::vector<TopoIndexInfo> sortedNodes = {{0, 0}};
692 if (links.size()) {
693 sortedNodes = WorkflowHelpers::topologicalSort(specs.size(), &(links[0].InputIdx), &(links[0].OutputIdx), sizeof(links[0]), links.size());
694 }
695 // This is to protect for the cases in which there is a loop in the
696 // definition of the inputs and of the outputs due to the
697 // way the forwarding creates hidden dependencies between processes.
698 // This should not happen, but apparently it does.
699 for (auto di = 0; di < specs.size(); ++di) {
700 auto fn = std::find_if(sortedNodes.begin(), sortedNodes.end(), [di](TopoIndexInfo const& info) {
701 return di == info.index;
702 });
703 if (fn == sortedNodes.end()) {
704 sortedNodes.push_back({(int)di, 0});
705 }
706 }
707 assert(specs.size() == sortedNodes.size());
709 std::sort(sortedNodes.begin(), sortedNodes.end());
710
711 std::vector<int> layerEntries(1024, 0);
712 std::vector<int> layerMax(1024, 0);
713 for (auto& node : sortedNodes) {
714 layerMax[node.layer < 1023 ? node.layer : 1023] += 1;
715 }
716
717 // FIXME: display nodes using topological sort
718 // Update positions
719 for (int si = 0; si < specs.size(); ++si) {
720 auto& node = sortedNodes[si];
721 assert(node.index == si);
722 int xpos = 40 + 240 * node.layer;
723 int ypos = 300 + (std::max(600, 60 * (int)layerMax[node.layer]) / (layerMax[node.layer] + 1)) * (layerEntries[node.layer] - layerMax[node.layer] / 2);
724 positions.push_back(NodePos{ImVec2(xpos, ypos)});
725 layerEntries[node.layer] += 1;
726 }
727 };
728
729 if (!inited) {
730 prepareChannelView(nodes, groups);
731 inited = true;
732 }
733
734 // Create our child canvas
735 ImGui::BeginGroup();
736 ImGui::Checkbox("Show grid", &show_grid);
737 ImGui::SameLine();
738 ImGui::Checkbox("Show legend", &show_legend);
739 ImGui::SameLine();
740 ImGui::Checkbox("Light mode", &state.topologyLightMode);
741 ImGui::SameLine();
742 if (ImGui::Button("Center")) {
743 scrolling = ImVec2(0., 0.);
744 zoom = 1.0f;
745 }
746 ImGui::SameLine();
747 if (ImGui::Button("-")) {
748 zoom = clampZoom(zoom / 1.15f);
749 }
750 ImGui::SameLine();
751 if (ImGui::Button("+")) {
752 zoom = clampZoom(zoom * 1.15f);
753 }
754 ImGui::SameLine();
755 if (ImGui::Button("Fit")) {
756 fitRequested = true;
757 }
758 ImGui::SameLine();
759 ImGui::Text("%.0f%%", zoom * 100.f);
760 ImGui::SameLine();
761 if (ImGui::Button("100%")) {
762 zoom = 1.0f;
763 }
764 ImGui::SameLine();
765 if (ImGui::Button("Export SVG")) {
766 if (exportTopologySVG("dpl-topology.svg", nodes, positions, links, infos, state.topologyLightMode)) {
767 snprintf(exportStatus, sizeof(exportStatus), "Exported dpl-topology.svg");
768 } else {
769 snprintf(exportStatus, sizeof(exportStatus), "Could not export dpl-topology.svg");
770 }
771 }
772 ImGui::SameLine();
773 if (ImGui::Button("Export DOT")) {
774 if (exportTopologyDOT("dpl-topology.dot", nodes, positions, links, infos, state.topologyLightMode)) {
775 snprintf(exportStatus, sizeof(exportStatus), "Exported dpl-topology.dot");
776 } else {
777 snprintf(exportStatus, sizeof(exportStatus), "Could not export dpl-topology.dot");
778 }
779 }
780 if (exportStatus[0] != '\0') {
781 ImGui::SameLine();
782 ImGui::TextUnformatted(exportStatus);
783 }
784 ImGui::SameLine();
785 if (state.leftPaneVisible == false && ImGui::Button("Show tree")) {
786 state.leftPaneVisible = true;
787 }
788 if (state.leftPaneVisible == true && ImGui::Button("Hide tree")) {
789 state.leftPaneVisible = false;
790 }
791 ImGui::SameLine();
792 if (state.bottomPaneVisible == false && ImGui::Button("Show metrics")) {
793 state.bottomPaneVisible = true;
794 }
795 if (state.bottomPaneVisible == true && ImGui::Button("Hide metrics")) {
796 state.bottomPaneVisible = false;
797 }
798 ImGui::SameLine();
799 if (state.rightPaneVisible == false && ImGui::Button("Show inspector")) {
800 state.rightPaneVisible = true;
801 }
802 if (state.rightPaneVisible == true && ImGui::Button("Hide inspector")) {
803 state.rightPaneVisible = false;
804 }
805 ImGui::Separator();
806 ImGui::EndGroup();
807 auto toolbarSize = ImGui::GetItemRectSize();
808 // Draw a list of nodes on the left side
809 bool open_context_menu = false;
810 int node_hovered_in_list = -1;
811 int node_hovered_in_scene = -1;
812 if (state.leftPaneVisible) {
813 ImGui::BeginChild("node_list", ImVec2(state.leftPaneSize, 0));
814 ImGui::Text("Workflows %d", groups.Size);
815 ImGui::Separator();
816 for (int groupId = 0; groupId < groups.Size; groupId++) {
817 Group* group = &groups[groupId];
818 if (ImGui::TreeNodeEx(group->name, ImGuiTreeNodeFlags_DefaultOpen)) {
819 for (int node_idx = 0; node_idx < nodes.Size; node_idx++) {
820 Node* node = &nodes[node_idx];
821 if (node->GroupID != groupId) {
822 continue;
823 }
824 ImGui::Indent(15);
825 ImGui::PushID(node->ID);
826 if (ImGui::Selectable(node->Name, node->ID == node_selected)) {
827 if (ImGui::IsMouseDoubleClicked(0)) {
828 controls[node_selected].logVisible = true;
829 }
830 node_selected = node->ID;
831 }
832 if (ImGui::IsItemHovered()) {
833 node_hovered_in_list = node->ID;
834 open_context_menu |= ImGui::IsMouseClicked(1);
835 }
836 ImGui::PopID();
837 ImGui::Unindent(15);
838 }
839 ImGui::TreePop();
840 }
841 }
842 ImGui::EndChild();
843 ImGui::SameLine();
844 }
845
846 ImGui::BeginGroup();
847
848 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
849 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
850 auto canvasBg = state.topologyLightMode ? (ImU32)ImColor(250, 250, 252, 255) : (ImU32)ImColor(44, 44, 46, 255);
851 auto canvasText = (ImU32)ImColor(235, 235, 245, 255); // nodes are always dark, so text is always light
852 ImGui::PushStyleColor(ImGuiCol_ChildBg, canvasBg);
853 ImGui::PushStyleColor(ImGuiCol_Text, canvasText);
854 ImVec2 graphSize = ImGui::GetWindowSize();
855 if (state.leftPaneVisible) {
856 graphSize.x -= state.leftPaneSize;
857 }
858 if (state.rightPaneVisible) {
859 graphSize.x -= state.rightPaneSize;
860 }
861 graphSize.y -= toolbarSize.y + 20;
862 ImGui::BeginChild("scrolling_region", graphSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse);
863 ImGui::PushItemWidth(graphSize.x);
864
865 ImVec2 canvasOrigin = ImGui::GetCursorScreenPos();
866 ImDrawList* draw_list = ImGui::GetWindowDrawList();
867 ImGuiIO& io = ImGui::GetIO();
868 if (fitRequested && nodes.Size > 0) {
869 ImVec2 minPos(positions[0].pos.x, positions[0].pos.y);
870 ImVec2 maxPos = positions[0].pos + nodes[0].Size;
871 for (int i = 1; i < nodes.Size; ++i) {
872 minPos.x = std::min(minPos.x, positions[i].pos.x);
873 minPos.y = std::min(minPos.y, positions[i].pos.y);
874 maxPos.x = std::max(maxPos.x, positions[i].pos.x + nodes[i].Size.x);
875 maxPos.y = std::max(maxPos.y, positions[i].pos.y + nodes[i].Size.y);
876 }
877 ImVec2 graphBounds = maxPos - minPos;
878 constexpr float FIT_PADDING = 80.f;
879 float fitZoomX = graphSize.x / std::max(graphBounds.x + FIT_PADDING, 1.f);
880 float fitZoomY = graphSize.y / std::max(graphBounds.y + FIT_PADDING, 1.f);
881 zoom = clampZoom(std::min(fitZoomX, fitZoomY));
882 ImVec2 visibleGraphSize = graphSize / zoom;
883 scrolling = minPos - (visibleGraphSize - graphBounds) / 2.f;
884 }
885 if (ImGui::IsWindowHovered() && io.MouseWheel != 0.f) {
886 ImVec2 mouseGraphPos = scrolling + (io.MousePos - canvasOrigin) / zoom;
887 float newZoom = clampZoom(zoom * (io.MouseWheel > 0.f ? 1.12f : 1.f / 1.12f));
888 scrolling = mouseGraphPos - (io.MousePos - canvasOrigin) / newZoom;
889 zoom = newZoom;
890 }
891 ImVec2 offset = canvasOrigin - scrolling * zoom;
892 // Number of layers we need. 2 per node, plus 2 for
893 // the background stuff.
894 draw_list->ChannelsSplit((nodes.Size + 2) * 2);
895
896 // Display grid
897 displayGrid(show_grid, scrolling, zoom, draw_list);
898
899 ImVec2 win_pos = ImGui::GetCursorScreenPos();
900 ImVec2 canvas_sz = ImGui::GetWindowSize();
901
902 // Arrow colors — richer amber in light mode to stand out on white canvas
903 const ImColor arrowBgColor = state.topologyLightMode ? ImColor(140, 80, 0) : ARROW_BACKGROUND_COLOR;
904 const ImColor arrowHalfColor = state.topologyLightMode ? ImColor(180, 110, 0) : ARROW_HALFGROUND_COLOR;
905 const ImColor arrowColor = state.topologyLightMode ? ImColor(220, 140, 0) : ARROW_COLOR;
906
907 // Display links but only if they are inside the view.
908 for (int link_idx = 0; link_idx < links.Size; link_idx++) {
909 // Do the geometry culling upfront.
910 NodeLink const& link = links[link_idx];
911 ImVec2 p1 = graphToScreen(NodePos::GetOutputSlotPos(nodes, positions, link.InputIdx, link.InputSlot), canvasOrigin, scrolling, zoom);
912 ImVec2 p2 = graphToScreen(NodePos::GetInputSlotPos(nodes, positions, link.OutputIdx, link.OutputSlot) + ImVec2(-3 * NODE_SLOT_RADIUS, 0), canvasOrigin, scrolling, zoom);
913
914 if ((p1.x > win_pos.x + canvas_sz.x + 50) && (p2.x > win_pos.x + canvas_sz.x + 50)) {
915 continue;
916 }
917
918 if ((p1.y > win_pos.y + canvas_sz.y + 50) && (p2.y > win_pos.y + canvas_sz.y + 50)) {
919 continue;
920 }
921
922 if ((p1.y < win_pos.y) && (p2.y < win_pos.y)) {
923 continue;
924 }
925
926 if ((p1.x < win_pos.x) && (p2.x < win_pos.x)) {
927 continue;
928 }
929 draw_list->ChannelsSetCurrent(0); // Background
930 auto color = arrowBgColor;
931 auto thickness = ARROW_BACKGROUND_THICKNESS;
932
933 bool p1Inside = false;
934 bool p2Inside = false;
935 if ((p1.x > win_pos.x) && (p1.x < (win_pos.x + canvas_sz.x)) && (p1.y < (win_pos.y + canvas_sz.y)) && (p1.y > win_pos.y)) {
936 p1Inside = true;
937 }
938
939 if ((p2.x > win_pos.x) && (p2.x < (win_pos.x + canvas_sz.x)) && (p2.y < (win_pos.y + canvas_sz.y)) && (p2.y > win_pos.y)) {
940 p2Inside = true;
941 }
942
943 if (p1Inside && p2Inside) {
944 // Whatever the two edges completely within the view, gets brighter color and foreground.
945 draw_list->ChannelsSetCurrent(2);
946 color = arrowColor;
947 thickness = ARROW_THICKNESS;
948 } else if (p1Inside || p2Inside) {
949 draw_list->ChannelsSetCurrent(1);
950 // Whenever one of the two ends is within the view, increase the color but keep the background
951 color = arrowHalfColor;
952 thickness = ARROW_HALFGROUND_THICKNESS;
953 }
954
955 // If something belongs to a selected node, it gets the selected color.
956 if (link.InputIdx == node_selected || link.OutputIdx == node_selected) {
957 auto foregroundLayer = (nodes.Size + 1) * 2 + 1;
958 draw_list->ChannelsSetCurrent(foregroundLayer);
959 color = ARROW_SELECTED_COLOR;
960 thickness = thickness + 2;
961 }
962
963 draw_list->AddBezierCurve(p1, p1 + ImVec2(+50 * zoom, 0), p2 + ImVec2(-50 * zoom, 0), p2, color, std::max(thickness * zoom, 1.f));
964 }
965
966 auto fgDrawList = ImGui::GetForegroundDrawList();
967 // Display nodes
968 for (int node_idx = 0; node_idx < nodes.Size; node_idx++) {
969 auto backgroundLayer = (node_idx + 1) * 2;
970 auto foregroundLayer = (node_idx + 1) * 2 + 1;
971 // Selected node goes to front
972 if (node_selected == node_idx) {
973 backgroundLayer = (nodes.Size + 1) * 2;
974 foregroundLayer = (nodes.Size + 1) * 2 + 1;
975 }
976 Node* node = &nodes[node_idx];
977 NodePos* pos = &positions[node_idx];
978 const DeviceInfo& info = infos[node_idx];
979
980 ImVec2 node_rect_min = graphToScreen(pos->pos, canvasOrigin, scrolling, zoom);
981
982 // Do not even start if we are sure the box is not visible
983 if ((node_rect_min.x > ImGui::GetCursorScreenPos().x + ImGui::GetWindowSize().x + 50) ||
984 (node_rect_min.y > ImGui::GetCursorScreenPos().y + ImGui::GetWindowSize().y + 50)) {
985 continue;
986 }
987
988 ImGui::PushID(node->ID);
989
990 // Display node contents first
991 draw_list->ChannelsSetCurrent(foregroundLayer);
992 bool old_any_active = ImGui::IsAnyItemActive();
993 ImGui::SetWindowFontScale(zoom);
994 ImGui::SetCursorScreenPos(node_rect_min + NODE_WINDOW_PADDING * zoom);
995 ImGui::BeginGroup(); // Lock horizontal position
996 ImGui::TextUnformatted(node->Name);
997 switch (info.maxLogLevel) {
998 case LogLevel::Critical:
999 ImGui::SameLine();
1000 ImGui::TextColored(ERROR_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_CIRCLE);
1001 break;
1002 case LogLevel::Error:
1003 ImGui::SameLine();
1004 ImGui::TextColored(ERROR_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_CIRCLE);
1005 break;
1006 case LogLevel::Alarm:
1007 ImGui::SameLine();
1008 ImGui::TextColored(WARNING_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_TRIANGLE);
1009 break;
1010 case LogLevel::Warning:
1011 ImGui::SameLine();
1012 ImGui::TextColored(WARNING_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_TRIANGLE);
1013 break;
1014 default:
1015 break;
1016 }
1017
1018 gui::displayDataRelayer(metricsInfos[node->ID], infos[node->ID], specs[node->ID], allStates[node->ID], ImVec2(200., 160.) * zoom, controls[node->ID].firstWnd);
1019 ImGui::EndGroup();
1020
1021 // Save the size of what we have emitted and whether any of the widgets are being used
1022 bool node_widgets_active = (!old_any_active && ImGui::IsAnyItemActive());
1023 float attemptX = std::max(ImGui::GetItemRectSize().x / zoom, 150.f);
1024 float attemptY = std::min(ImGui::GetItemRectSize().y / zoom, 128.f);
1025 node->Size = ImVec2(attemptX, attemptY) + NODE_WINDOW_PADDING + NODE_WINDOW_PADDING;
1026 ImVec2 node_screen_size = node->Size * zoom;
1027 ImVec2 node_rect_max = node_rect_min + node_screen_size;
1028 ImVec2 node_rect_title = node_rect_min + ImVec2(node_screen_size.x, 24 * zoom);
1029
1030 if (node_rect_min.x > 20 + 2 * NODE_WINDOW_PADDING.x + state.leftPaneSize + graphSize.x) {
1031 ImGui::PopID();
1032 continue;
1033 }
1034 if (node_rect_min.y > 20 + 2 * NODE_WINDOW_PADDING.y + toolbarSize.y + graphSize.y) {
1035 ImGui::PopID();
1036 continue;
1037 }
1038
1039 // Display node box
1040 draw_list->ChannelsSetCurrent(backgroundLayer); // Background
1041 ImGui::SetCursorScreenPos(node_rect_min);
1042 ImGui::InvisibleButton("node", node_screen_size);
1043 if (ImGui::IsItemHovered()) {
1044 node_hovered_in_scene = node->ID;
1045 open_context_menu |= ImGui::IsMouseClicked(1);
1046 if (ImGui::IsMouseDoubleClicked(0)) {
1047 controls[node->ID].logVisible = true;
1048 }
1049 }
1050 bool node_moving_active = ImGui::IsItemActive();
1051 if (node_widgets_active || node_moving_active) {
1052 node_selected = node->ID;
1053 }
1054 if (node_moving_active && ImGui::IsMouseDragging(0)) {
1055 pos->pos = pos->pos + ImGui::GetIO().MouseDelta / zoom;
1056 }
1057 if (ImGui::IsWindowHovered() && !node_moving_active && ImGui::IsMouseDragging(0)) {
1058 scrolling = scrolling - ImVec2(ImGui::GetIO().MouseDelta.x / 4.f, ImGui::GetIO().MouseDelta.y / 4.f) / zoom;
1059 }
1060
1061 auto nodeBg = decideColorForNode(info, state.topologyLightMode);
1062
1063 auto hovered = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID));
1064 ImVec4 nodeBgColor = hovered ? nodeBg.hovered : nodeBg.normal;
1065 ImVec4 nodeTitleColor = hovered ? nodeBg.title_hovered : nodeBg.title;
1066 ImU32 node_bg_color = ImGui::ColorConvertFloat4ToU32(nodeBgColor);
1067 ImU32 node_title_color = ImGui::ColorConvertFloat4ToU32(nodeTitleColor);
1068
1069 draw_list->AddRectFilled(node_rect_min + ImVec2(3.f, 3.f) * zoom, node_rect_max + ImVec2(3.f, 3.f) * zoom, ImColor(0, 0, 0, 70), 4.0f * zoom);
1070 draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f * zoom);
1071 draw_list->AddRectFilled(node_rect_min, node_rect_title, node_title_color, 4.0f * zoom);
1072 draw_list->AddRect(node_rect_min, node_rect_max, NODE_BORDER_COLOR, 4.0f * zoom, 0, std::max(NODE_BORDER_THICKNESS * zoom, 1.f));
1073
1074 for (int slot_idx = 0; slot_idx < node->InputsCount; slot_idx++) {
1075 draw_list->ChannelsSetCurrent(backgroundLayer); // Background
1076 ImVec2 p1(-3 * NODE_SLOT_RADIUS, NODE_SLOT_RADIUS), p2(-3 * NODE_SLOT_RADIUS, -NODE_SLOT_RADIUS), p3(0, 0);
1077 auto slotPos = NodePos::GetInputSlotPos(nodes, positions, node_idx, slot_idx);
1078 auto slotScreenPos = graphToScreen(slotPos, canvasOrigin, scrolling, zoom);
1079 auto pp1 = p1 * zoom + slotScreenPos;
1080 auto pp2 = p2 * zoom + slotScreenPos;
1081 auto pp3 = p3 * zoom + slotScreenPos;
1082 auto color = arrowColor;
1083 if (node_idx == node_selected) {
1084 color = ARROW_SELECTED_COLOR;
1085 }
1086 draw_list->AddTriangleFilled(pp1, pp2, pp3, color);
1087 draw_list->AddCircleFilled(slotScreenPos, NODE_SLOT_RADIUS * zoom, INPUT_SLOT_COLOR);
1088 }
1089
1090 draw_list->ChannelsSetCurrent(foregroundLayer);
1091 MetricLabelsContext context{&nodes, node_idx, &positions, draw_list, canvasOrigin, scrolling, zoom};
1093 MetricsPainter<int, uint64_t, MetricLabelsContext>::draw(
1094 "input_labels",
1095 offset,
1096 ImVec2{node->Size.x, node->Size.y},
1097 context,
1098 MetricsPainter<int, uint64_t, MetricLabelsContext>::metric1D(),
1099 MetricsPainter<int, uint64_t, MetricLabelsContext>::simpleRecord<int>(),
1100 MetricsPainter<int, uint64_t, MetricLabelsContext>::items2D(info.inputChannelMetricsViewIndex),
1101 MetricsPainter<int, uint64_t, MetricLabelsContext>::latestMetric<uint64_t>(metricsInfos[node->ID], info.inputChannelMetricsViewIndex),
1102 MetricsPainter<int, uint64_t, MetricLabelsContext>::simpleValue<uint64_t>(),
1103 MetricsPainter<int, uint64_t, MetricLabelsContext>::colorPalette(std::vector<ImU32>{{ImColor(0, 100, 0, 255), ImColor(0, 0, 100, 255), ImColor(100, 0, 0, 255)}}, 0, 3),
1104 [](int, int item, int value, ImU32 color, MetricLabelsContext const& context) {
1105 auto draw_list = context.draw_list;
1106 auto& nodes = *context.nodes;
1107 auto& positions = *context.positions;
1108 auto slotPos = NodePos::GetInputSlotPos(nodes, positions, context.nodeIdx, item);
1109 auto slotScreenPos = graphToScreen(slotPos, context.canvasOrigin, context.scrolling, context.zoom);
1110 Node* node = &nodes[context.nodeIdx];
1111 auto& label = node->oldestPossibleInput[item];
1112 // Avoid recomputing if the value is the same.
1113 if (label.value != value) {
1114 label.value = value;
1115 snprintf(label.buffer, sizeof(label.buffer), "%d", value);
1116 }
1117 label.textSize = ImGui::CalcTextSize(label.buffer).x;
1118 draw_list->AddRectFilled(slotScreenPos - ImVec2{node->oldestPossibleInput[item].textSize + 5.f * NODE_SLOT_RADIUS * context.zoom, 2 * NODE_SLOT_RADIUS * context.zoom},
1119 slotScreenPos + ImVec2{-4.5f * NODE_SLOT_RADIUS * context.zoom, 2 * NODE_SLOT_RADIUS * context.zoom}, NODE_LABEL_BACKGROUND_COLOR, 2.f * context.zoom, ImDrawFlags_RoundCornersAll);
1120 draw_list->AddText(nullptr, 12 * context.zoom,
1121 slotScreenPos - ImVec2{node->oldestPossibleInput[item].textSize + 4.5f * NODE_SLOT_RADIUS * context.zoom, 2 * NODE_SLOT_RADIUS * context.zoom},
1122 NODE_LABEL_TEXT_COLOR,
1123 node->oldestPossibleInput[item].buffer);
1124 });
1125
1126 for (int slot_idx = 0; slot_idx < node->OutputsCount; slot_idx++) {
1127 draw_list->AddCircleFilled(graphToScreen(NodePos::GetOutputSlotPos(nodes, positions, node_idx, slot_idx), canvasOrigin, scrolling, zoom), NODE_SLOT_RADIUS * zoom, OUTPUT_SLOT_COLOR);
1128 }
1129
1130 MetricsPainter<int, uint64_t, MetricLabelsContext>::draw(
1131 "output_labels",
1132 offset,
1133 ImVec2{node->Size.x, node->Size.y},
1134 context,
1135 MetricsPainter<int, uint64_t, MetricLabelsContext>::metric1D(),
1136 MetricsPainter<int, uint64_t, MetricLabelsContext>::simpleRecord<int>(),
1137 MetricsPainter<int, uint64_t, MetricLabelsContext>::items2D(info.outputChannelMetricsViewIndex),
1138 MetricsPainter<int, uint64_t, MetricLabelsContext>::latestMetric<uint64_t>(metricsInfos[node->ID], info.outputChannelMetricsViewIndex),
1139 MetricsPainter<int, uint64_t, MetricLabelsContext>::simpleValue<uint64_t>(),
1140 MetricsPainter<int, uint64_t, MetricLabelsContext>::colorPalette(std::vector<ImU32>{{ImColor(0, 100, 0, 255), ImColor(0, 0, 100, 255), ImColor(100, 0, 0, 255)}}, 0, 3),
1141 [](int, int item, int value, ImU32 color, MetricLabelsContext const& context) {
1142 auto draw_list = context.draw_list;
1143 auto& nodes = *context.nodes;
1144 auto& positions = *context.positions;
1145 auto slotPos = NodePos::GetOutputSlotPos(nodes, positions, context.nodeIdx, item);
1146 auto slotScreenPos = graphToScreen(slotPos, context.canvasOrigin, context.scrolling, context.zoom);
1147 Node* node = &nodes[context.nodeIdx];
1148 auto& label = node->oldestPossibleOutput[item];
1149 // Avoid recomputing if the value is the same.
1150 if (label.value != value) {
1151 label.value = value;
1152 snprintf(label.buffer, sizeof(label.buffer), "%d", value);
1153 }
1154 label.textSize = ImGui::CalcTextSize(label.buffer).x;
1155 auto rectTL = ImVec2{4.5f * NODE_SLOT_RADIUS * context.zoom, -2 * NODE_SLOT_RADIUS * context.zoom};
1156 auto rectBR = ImVec2{node->oldestPossibleOutput[item].textSize + 5.f * NODE_SLOT_RADIUS * context.zoom, 2 * NODE_SLOT_RADIUS * context.zoom};
1157 draw_list->AddRectFilled(slotScreenPos + rectTL,
1158 slotScreenPos + rectBR, NODE_LABEL_BACKGROUND_COLOR, 2.f * context.zoom, ImDrawFlags_RoundCornersAll);
1159 draw_list->AddText(nullptr, 12 * context.zoom,
1160 slotScreenPos + rectTL,
1161 NODE_LABEL_TEXT_COLOR,
1162 node->oldestPossibleOutput[item].buffer);
1163 });
1164
1165 ImGui::PopID();
1166 }
1167 ImGui::SetWindowFontScale(1.0f);
1168 draw_list->ChannelsMerge();
1169 displayLegend(show_legend, offset, draw_list);
1170
1171 // Open context menu
1172 if (!ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && ImGui::IsMouseClicked(1)) {
1173 node_selected = node_hovered_in_list = node_hovered_in_scene = -1;
1174 open_context_menu = true;
1175 }
1176 if (open_context_menu) {
1177 ImGui::OpenPopup("context_menu");
1178 if (node_hovered_in_list != -1) {
1179 node_selected = node_hovered_in_list;
1180 }
1181 if (node_hovered_in_scene != -1) {
1182 node_selected = node_hovered_in_scene;
1183 }
1184 }
1185
1186 // Scrolling
1187 // if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(2, 0.0f))
1188 // scrolling = scrolling - ImGui::GetIO().MouseDelta;
1189
1190 ImGui::PopItemWidth();
1191 ImGui::EndChild();
1192 ImGui::PopStyleColor(2);
1193 ImGui::PopStyleVar(2);
1194 ImGui::EndGroup();
1195
1196 if (state.rightPaneVisible) {
1197 ImGui::SameLine();
1198 ImGui::BeginGroup();
1199 ImGui::BeginChild("inspector");
1200 ImGui::TextUnformatted("Device Inspector");
1201 ImGui::Separator();
1202 if (node_selected != -1) {
1203 auto& node = nodes[node_selected];
1204 auto& spec = specs[node_selected];
1205 auto& states = allStates[node_selected];
1206 auto& control = controls[node_selected];
1207 auto& info = infos[node_selected];
1208 auto& metrics = metricsInfos[node_selected];
1209 auto& group = groups[node.GroupID];
1210 auto& metadatum = metadata[group.metadataId];
1211
1212 if (state.rightPaneVisible) {
1213 gui::displayDeviceInspector(spec, info, states, metrics, metadatum, control);
1214 }
1215 } else {
1216 ImGui::TextWrapped("Select a node in the topology to display information about it");
1217 }
1218 ImGui::EndChild();
1219 ImGui::EndGroup();
1220 }
1221 ImGui::End();
1222}
1223
1224} // namespace o2::framework::gui
1225#pragma GGC diagnostic pop
benchmark::State & state
o2::monitoring::tags::Value Value
std::unique_ptr< expressions::Node > node
#define MAX_GROUP_NAME_SIZE
int32_t i
constexpr int p2()
constexpr int p1()
constexpr to accelerate the coordinates changing
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
uint16_t pos
Definition RawData.h:3
uint32_t c
Definition RawData.h:2
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
GLsizei GLuint * groups
Definition glcorearb.h:3984
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint buffer
Definition glcorearb.h:655
GLsizeiptr size
Definition glcorearb.h:659
GLuint color
Definition glcorearb.h:1272
GLuint const GLchar * name
Definition glcorearb.h:781
GLsizei GLenum const void GLuint GLsizei GLfloat * metrics
Definition glcorearb.h:5500
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLboolean * data
Definition glcorearb.h:298
GLintptr offset
Definition glcorearb.h:660
GLboolean GLuint group
Definition glcorearb.h:3991
GLuint GLsizei const GLchar * label
Definition glcorearb.h:2519
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLuint id
Definition glcorearb.h:650
GLuint * states
Definition glcorearb.h:4932
Vec3 operator-(const Vec3 &firstVector, const Vec3 &secondVector)
double component(const Vec3 &vector, int dimension)
const GBTLinkAttributes links[kNGBTLinks]
Definition RawWriter.h:54
std::variant< OriginValueMatcher, DescriptionValueMatcher, SubSpecificationTypeValueMatcher, std::unique_ptr< DataDescriptorMatcher >, ConstantValueMatcher, StartTimeValueMatcher > Node
State for the main GUI window.
void showTopologyNodeGraph(WorkspaceGUIState &state, std::vector< DeviceInfo > const &infos, std::vector< DeviceSpec > const &specs, std::vector< DataProcessingStates > const &allStates, std::vector< DataProcessorInfo > const &metadata, std::vector< DeviceControl > &controls, std::vector< DeviceMetricsInfo > const &metricsInfos)
void displayDeviceInspector(DeviceSpec const &spec, DeviceInfo const &info, DataProcessingStates const &states, DeviceMetricsInfo const &metrics, DataProcessorInfo const &metadata, DeviceControl &control)
Helper to display information about a device.
void displayDataRelayer(DeviceMetricsInfo const &, DeviceInfo const &, DeviceSpec const &spec, DataProcessingStates const &states, ImVec2 const &size, int &v)
View of the DataRelayer metrics for a given DeviceInfo.
@ EndOfStreaming
End of streaming requested, but not notified.
@ Streaming
Data is being processed.
@ Idle
End of streaming notified.
o2::framework::LogParsingHelpers::LogLevel LogLevel
D const SVectorGPU< T, D > & rhs
Definition SMatrixGPU.h:193
MultPolicyGPU< T, R1, R2 >::RepType operator*(const SMatrixGPU< T, D1, D, R1 > &lhs, const SMatrixGPU< T, D, D2, R2 > &rhs)
Definition SMatrixGPU.h:750
std::array< int, 23 > p3
constexpr double thickness
Definition Specs.h:39
BinCenterView< AxisIterator > operator+(BinCenterView< AxisIterator > lhs, int n)
std::string filename()
double getValue(DPVAL dp)
LogLevel
Possible log levels for device log entries.
static const ImVec4 SHADED_GREEN
static const ImVec4 DARK_GREEN
static const ImVec4 SHADED_YELLOW
static const ImVec4 SHADED_RED
static const ImVec4 LIGHT_GRAY
static const ImVec4 DARK_YELLOW
static std::vector< TopoIndexInfo > topologicalSort(size_t nodeCount, int const *edgeIn, int const *edgeOut, size_t byteStride, size_t edgesCount)
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< int > row