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 <vector>
27
28#pragma GCC diagnostic push
29#pragma GCC diagnostic ignored "-Wpedantic"
30static inline ImVec2 operator+(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x + rhs.x, lhs.y + rhs.y); }
31static inline ImVec2 operator-(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x - rhs.x, lhs.y - rhs.y); }
32
33namespace o2::framework::gui
34{
35struct NodeColor {
36 ImVec4 normal;
37 ImVec4 hovered;
38 ImVec4 title;
40};
41
43
44NodeColor decideColorForNode(const DeviceInfo& info, bool lightMode)
45{
46 if (lightMode) {
47 // Dark-on-bright: rich medium-dark cards on a white canvas, white text on nodes
48 if (info.active == false) {
49 return NodeColor{
50 .normal = ImVec4(0xb5 / 255.f, 0x26 / 255.f, 0x18 / 255.f, 1), // dark crimson
51 .hovered = ImVec4(0xc2 / 255.f, 0x2d / 255.f, 0x1d / 255.f, 1)};
52 }
53 switch (info.streamingState) {
55 return NodeColor{
56 .normal = ImVec4(0x8c / 255.f, 0x6c / 255.f, 0x00 / 255.f, 1), // dark amber
57 .hovered = ImVec4(0x9e / 255.f, 0x7a / 255.f, 0x00 / 255.f, 1),
58 .title = ImVec4(0x6e / 255.f, 0x54 / 255.f, 0x00 / 255.f, 1),
59 .title_hovered = ImVec4(0x5a / 255.f, 0x44 / 255.f, 0x00 / 255.f, 1)};
61 return NodeColor{
62 .normal = ImVec4(0x1a / 255.f, 0x80 / 255.f, 0x40 / 255.f, 1), // dark forest green
63 .hovered = ImVec4(0x22 / 255.f, 0x8b / 255.f, 0x47 / 255.f, 1),
64 .title = ImVec4(0x11 / 255.f, 0x60 / 255.f, 0x2e / 255.f, 1),
65 .title_hovered = ImVec4(0x0a / 255.f, 0x4d / 255.f, 0x23 / 255.f, 1)};
67 default:
68 return NodeColor{
69 .normal = ImVec4(0x3a / 255.f, 0x3a / 255.f, 0x3c / 255.f, 1), // macOS tertiary dark
70 .hovered = ImVec4(0x48 / 255.f, 0x48 / 255.f, 0x4a / 255.f, 1),
71 .title = ImVec4(0x2c / 255.f, 0x2c / 255.f, 0x2e / 255.f, 1),
72 .title_hovered = ImVec4(0x1c / 255.f, 0x1c / 255.f, 0x1e / 255.f, 1)};
73 }
74 }
75 if (info.active == false) {
76 return NodeColor{
79 }
80 switch (info.streamingState) {
82 return NodeColor{
84 .hovered = PaletteHelpers::YELLOW,
86 .title_hovered = PaletteHelpers::DARK_YELLOW};
88 return NodeColor{
90 .hovered = PaletteHelpers::GREEN,
91 .title = PaletteHelpers::GREEN,
92 .title_hovered = PaletteHelpers::DARK_GREEN};
94 default:
95 return NodeColor{
98 .title = PaletteHelpers::GRAY,
99 .title_hovered = PaletteHelpers::BLACK};
100 }
101}
102
104const static ImColor INPUT_SLOT_COLOR = {150, 150, 150, 150};
105const static ImColor OUTPUT_SLOT_COLOR = {150, 150, 150, 150};
106const static ImColor NODE_LABEL_BACKGROUND_COLOR = {45, 45, 45, 255};
107const static ImColor NODE_LABEL_TEXT_COLOR = {244, 244, 244, 255};
108const static ImVec4& ERROR_MESSAGE_COLOR = PaletteHelpers::RED;
109const static ImVec4& WARNING_MESSAGE_COLOR = PaletteHelpers::YELLOW;
110const static ImColor ARROW_BACKGROUND_COLOR = {100, 100, 0};
111const static ImColor ARROW_HALFGROUND_COLOR = {170, 170, 70};
112const static ImColor ARROW_COLOR = {200, 200, 100};
113const static ImColor ARROW_SELECTED_COLOR = {200, 0, 100};
114const static ImU32 GRID_COLOR = ImColor(150, 150, 150, 80);
115const static ImColor NODE_BORDER_COLOR = {100, 100, 100};
116const static ImColor LEGEND_COLOR = {100, 100, 100};
117
119const static float GRID_SZ = 64.0f;
120const static float ARROW_BACKGROUND_THICKNESS = 1.f;
121const static float ARROW_HALFGROUND_THICKNESS = 2.f;
122const static float ARROW_THICKNESS = 3.f;
123const static float NODE_BORDER_THICKNESS = 4.f;
124const static ImVec4 LEGEND_BACKGROUND_COLOR = {0.125, 0.180, 0.196, 1};
125
126const static ImVec4 SLOT_EMPTY_COLOR = {0.275, 0.275, 0.275, 1.};
127const static ImVec4& SLOT_PENDING_COLOR = PaletteHelpers::RED;
128const static ImVec4& SLOT_DISPATCHED_COLOR = PaletteHelpers::YELLOW;
129const static ImVec4& SLOT_DONE_COLOR = PaletteHelpers::GREEN;
130
132const float NODE_SLOT_RADIUS = 4.0f;
133const ImVec2 NODE_WINDOW_PADDING(8.0f, 8.0f);
134
136void displayGrid(bool show_grid, ImVec2 offset, ImDrawList* draw_list)
137{
138 if (show_grid == false) {
139 return;
140 }
141 ImVec2 win_pos = ImGui::GetCursorScreenPos();
142 ImVec2 canvas_sz = ImGui::GetWindowSize();
143 for (float x = fmodf(offset.x, GRID_SZ); x < canvas_sz.x; x += GRID_SZ) {
144 draw_list->AddLine(ImVec2(x, 0.0f) + win_pos, ImVec2(x, canvas_sz.y) + win_pos, GRID_COLOR);
145 }
146 for (float y = fmodf(offset.y, GRID_SZ); y < canvas_sz.y; y += GRID_SZ) {
147 draw_list->AddLine(ImVec2(0.0f, y) + win_pos, ImVec2(canvas_sz.x, y) + win_pos, GRID_COLOR);
148 }
149}
150
151void displayLegend(bool show_legend, ImVec2 offset, ImDrawList* draw_list)
152{
153 if (show_legend == false) {
154 return;
155 }
156 struct LegendItem {
157 std::string label;
158 const ImVec4& color;
159 };
160 static auto legend = {
161 LegendItem{" Slot empty", SLOT_EMPTY_COLOR},
162 LegendItem{" Slot pending", SLOT_PENDING_COLOR},
163 LegendItem{" Slot dispatched", SLOT_DISPATCHED_COLOR},
164 LegendItem{" Slot done", SLOT_DONE_COLOR},
165 };
166 ImGui::PushStyleColor(ImGuiCol_WindowBg, LEGEND_BACKGROUND_COLOR);
167 ImGui::PushStyleColor(ImGuiCol_TitleBg, LEGEND_BACKGROUND_COLOR);
168 ImGui::PushStyleColor(ImGuiCol_ResizeGrip, 0);
169
170 ImGui::Begin("Legend");
171 ImGui::Dummy(ImVec2(0.0f, 10.0f));
172 for (auto [label, color] : legend) {
173 ImVec2 vMin = ImGui::GetWindowPos() + ImGui::GetCursorPos() + ImVec2(9, 0);
174 ImVec2 vMax = vMin + ImGui::CalcTextSize(" ");
175 ImGui::PushStyleColor(ImGuiCol_ChildBg, color);
176 ImGui::GetWindowDrawList()->AddRectFilled(vMin, vMax, ImColor(color));
177 ImGui::GetWindowDrawList()->AddRect(vMin, vMax, GRID_COLOR);
178 ImGui::Text("%s", label.data());
179 ImGui::PopStyleColor();
180 }
181 ImGui::End();
182 ImGui::PopStyleColor(3);
183}
184
185#define MAX_GROUP_NAME_SIZE 128
186
187// Private helper struct to keep track of node groups
188struct Group {
189 int ID;
192 Group(int id, char const* n, size_t mid)
193 {
194 ID = id;
195 strncpy(name, n, MAX_GROUP_NAME_SIZE);
196 name[MAX_GROUP_NAME_SIZE - 1] = 0;
197 metadataId = mid;
198 }
199};
200
201constexpr int MAX_SLOTS = 512;
202constexpr int MAX_INPUT_VALUE_SIZE = 24;
203
205 size_t value;
206 char buffer[MAX_INPUT_VALUE_SIZE] = "unknown";
207 float textSize = ImGui::CalcTextSize("unknown").x;
208};
209
211 size_t value;
212 char buffer[MAX_INPUT_VALUE_SIZE] = "unknown";
213 float textSize = ImGui::CalcTextSize("unknown").x;
214};
215
216// Private helper struct for the graph model
217struct Node {
218 int ID;
220 char Name[64];
221 ImVec2 Size;
222 float Value;
223 ImVec4 Color;
227
228 Node(int id, int groupID, char const* name, float value, const ImVec4& color, int inputs_count, int outputs_count)
229 {
230 ID = id;
231 GroupID = groupID;
232 strncpy(Name, name, 63);
233 Name[63] = 0;
234 Value = value;
235 Color = color;
236 InputsCount = inputs_count;
237 OutputsCount = outputs_count;
238 }
239};
240
241// Private helper struct for the layout of the graph
242struct NodePos {
243 ImVec2 pos;
244 static ImVec2 GetInputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
245 {
246 ImVec2 const& pos = positions[nodeId].pos;
247 ImVec2 const& size = infos[nodeId].Size;
248 float inputsCount = infos[nodeId].InputsCount;
249 return ImVec2(pos.x, pos.y + size.y * ((float)slot_no + 1) / (inputsCount + 1));
250 }
251 static ImVec2 GetOutputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
252 {
253 ImVec2 const& pos = positions[nodeId].pos;
254 ImVec2 const& size = infos[nodeId].Size;
255 float outputsCount = infos[nodeId].OutputsCount;
256 return ImVec2(pos.x + size.x, pos.y + size.y * ((float)slot_no + 1) / (outputsCount + 1));
257 }
258};
259
260// Private helper struct for the edges in the graph
261struct NodeLink {
263
264 NodeLink(int input_idx, int input_slot, int output_idx, int output_slot)
265 {
266 InputIdx = input_idx;
267 InputSlot = input_slot;
268 OutputIdx = output_idx;
269 OutputSlot = output_slot;
270 }
271};
272
274template <typename RECORD, typename ITEM, typename CONTEXT>
276 using NumRecordsCallback = std::function<size_t(void)>;
277 using RecordCallback = std::function<RECORD(size_t)>;
278 using NumItemsCallback = std::function<size_t(RECORD const&)>;
279 using ItemCallback = std::function<ITEM const&(RECORD const&, size_t)>;
280 using ValueCallback = std::function<int(ITEM const&)>;
281 using ColorCallback = std::function<ImU32(int value)>;
282 using ContextCallback = std::function<CONTEXT&()>;
283 using PaintCallback = std::function<void(int row, int column, int value, ImU32 color, CONTEXT const& context)>;
284
285 static void draw(const char* name,
286 ImVec2 const& offset,
287 ImVec2 const& sizeHint,
288 CONTEXT const& context,
289 NumRecordsCallback const& getNumRecords,
290 RecordCallback const& getRecord,
291 NumItemsCallback const& getNumItems,
292 ItemCallback const& getItem,
293 ValueCallback const& getValue,
294 ColorCallback const& getColor,
295 PaintCallback const& describeCell)
296 {
297 for (size_t ri = 0; ri < getNumRecords(); ++ri) {
298 auto record = getRecord(ri);
299 for (size_t ii = 0; ii < getNumItems(record); ++ii) {
300 auto item = getItem(record, ii);
301 int value = getValue(item);
302 ImU32 color = getColor(value);
303 ImVec2 pos = ImGui::GetCursorScreenPos();
304 describeCell(ri, ii, value, color, context);
305 }
306 }
307 }
308
310 {
311 return []() -> size_t { return 1UL; };
312 }
313
315 {
316 return [&viewIndex]() -> size_t {
317 if (viewIndex.isComplete()) {
318 return viewIndex.w;
319 }
320 return 0;
321 };
322 }
323
325 {
326 return [](RECORD const& record) -> size_t { return 1UL; };
327 }
328
330 {
331 return [&viewIndex](int record) -> int {
332 if (viewIndex.isComplete()) {
333 return viewIndex.h;
334 }
335 return 0;
336 };
337 }
338
339 template <typename T>
341 Metric2DViewIndex const& viewIndex)
342 {
343 return [&metrics, &viewIndex](RECORD const& record, size_t i) -> ITEM const& {
344 // Calculate the index in the viewIndex.
345 auto idx = record * viewIndex.h + i;
346 assert(viewIndex.indexes.size() > idx);
347 MetricInfo const& metricInfo = metrics.metrics[viewIndex.indexes[idx]];
348 auto& data = DeviceMetricsInfoHelpers::get<T, metricStorageSize<T>()>(metrics, metricInfo.storeIdx);
349 return data[(metricInfo.pos - 1) % data.size()];
350 };
351 }
352
353 template <typename T>
355 {
356 return [](size_t i) -> int {
357 return i;
358 };
359 }
360
361 template <typename T>
363 {
364 return [](ITEM const& item) -> T { return item; };
365 }
366
367 static ColorCallback colorPalette(std::vector<ImU32> const& colors, int minValue, int maxValue)
368 {
369 return [colors, minValue, maxValue](int const& value) -> ImU32 {
370 if (value < minValue) {
371 return colors[0];
372 } else if (value > maxValue) {
373 return colors[colors.size() - 1];
374 } else {
375 int idx = (value - minValue) * (colors.size() - 1) / (maxValue - minValue);
376 return colors[idx];
377 }
378 };
379 }
380};
381
384 ImVector<Node>* nodes;
386 ImVector<NodePos>* positions;
387 ImDrawList* draw_list;
388 ImVec2 offset;
389};
390
392 std::vector<DeviceInfo> const& infos,
393 std::vector<DeviceSpec> const& specs,
394 std::vector<DataProcessingStates> const& allStates,
395 std::vector<DataProcessorInfo> const& metadata,
396 std::vector<DeviceControl>& controls,
397 std::vector<DeviceMetricsInfo> const& metricsInfos)
398{
399 ImGui::SetNextWindowPos(ImVec2(0, 0), 0);
400 if (state.bottomPaneVisible) {
401 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y - state.bottomPaneSize), 0);
402 } else {
403 ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y), 0);
404 }
405
406 ImGui::Begin("Physical topology view", nullptr, ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
407
408 static ImVector<Node> nodes;
409 static ImVector<Group> groups;
410 static ImVector<NodeLink> links;
411 static ImVector<NodePos> positions;
412
413 static bool inited = false;
414 static ImVec2 scrolling = ImVec2(0.0f, 0.0f);
415 static bool show_grid = true;
416 static bool show_legend = true;
417 static int node_selected = -1;
418
419 auto prepareChannelView = [&specs, &metricsInfos, &metadata](ImVector<Node>& nodeList, ImVector<Group>& groupList) {
420 struct LinkInfo {
421 int specId;
422 int outputId;
423 };
424 std::map<std::string, LinkInfo> linkToIndex;
425 for (int si = 0; si < specs.size(); ++si) {
426 int oi = 0;
427 for (auto&& output : specs[si].outputChannels) {
428 linkToIndex.insert(std::make_pair(output.name, LinkInfo{si, oi}));
429 oi += 1;
430 }
431 }
432 // Prepare the list of groups.
433 std::string workflow = "Ungrouped";
434 int groupId = 0;
435 for (size_t mi = 0; mi < metadata.size(); ++mi) {
436 auto const& metadatum = metadata[mi];
437 if (metadatum.executable == workflow) {
438 continue;
439 }
440 workflow = metadatum.executable;
441 char* groupBasename = strrchr(workflow.data(), '/');
442 char const* groupName = groupBasename ? groupBasename + 1 : workflow.data();
443 bool hasDuplicate = false;
444 for (size_t gi = 0; gi < groupList.Size; ++gi) {
445 if (strncmp(groupName, groupList[gi].name, MAX_GROUP_NAME_SIZE - 1) == 0) {
446 hasDuplicate = true;
447 break;
448 }
449 }
450 if (hasDuplicate == false) {
451 groupList.push_back(Group(groupId++, groupName, mi));
452 }
453 }
454 // Do matching between inputs and outputs
455 for (int si = 0; si < specs.size(); ++si) {
456 auto& spec = specs[si];
457 int groupId = 0;
458
459 auto metadatum = std::find_if(metadata.begin(), metadata.end(),
460 [&name = spec.name](DataProcessorInfo const& info) { return info.name == name; });
461
462 for (size_t gi = 0; gi < groupList.Size; ++gi) {
463 if (metadatum == metadata.end()) {
464 break;
465 }
466 const char* groupName = strrchr(metadatum->executable.data(), '/');
467 if (strncmp(groupList[gi].name, groupName ? groupName + 1 : metadatum->executable.data(), 127) == 0) {
468 groupId = gi;
469 break;
470 }
471 }
472 nodeList.push_back(Node(si, groupId, spec.id.c_str(), 0.5f,
473 ImColor(255, 100, 100),
474 spec.inputChannels.size(),
475 spec.outputChannels.size()));
476 int ii = 0;
477 for (auto& input : spec.inputChannels) {
478 auto const& outName = input.name;
479 auto const& out = linkToIndex.find(input.name);
480 if (out == linkToIndex.end()) {
481 LOG(error) << "Could not find suitable node for " << outName;
482 continue;
483 }
484 links.push_back(NodeLink{out->second.specId, out->second.outputId, si, ii});
485 ii += 1;
486 }
487 }
488
489 // ImVector does boudary checks, so I bypass the case there is no
490 // edges.
491 std::vector<TopoIndexInfo> sortedNodes = {{0, 0}};
492 if (links.size()) {
493 sortedNodes = WorkflowHelpers::topologicalSort(specs.size(), &(links[0].InputIdx), &(links[0].OutputIdx), sizeof(links[0]), links.size());
494 }
495 // This is to protect for the cases in which there is a loop in the
496 // definition of the inputs and of the outputs due to the
497 // way the forwarding creates hidden dependencies between processes.
498 // This should not happen, but apparently it does.
499 for (auto di = 0; di < specs.size(); ++di) {
500 auto fn = std::find_if(sortedNodes.begin(), sortedNodes.end(), [di](TopoIndexInfo const& info) {
501 return di == info.index;
502 });
503 if (fn == sortedNodes.end()) {
504 sortedNodes.push_back({(int)di, 0});
505 }
506 }
507 assert(specs.size() == sortedNodes.size());
509 std::sort(sortedNodes.begin(), sortedNodes.end());
510
511 std::vector<int> layerEntries(1024, 0);
512 std::vector<int> layerMax(1024, 0);
513 for (auto& node : sortedNodes) {
514 layerMax[node.layer < 1023 ? node.layer : 1023] += 1;
515 }
516
517 // FIXME: display nodes using topological sort
518 // Update positions
519 for (int si = 0; si < specs.size(); ++si) {
520 auto& node = sortedNodes[si];
521 assert(node.index == si);
522 int xpos = 40 + 240 * node.layer;
523 int ypos = 300 + (std::max(600, 60 * (int)layerMax[node.layer]) / (layerMax[node.layer] + 1)) * (layerEntries[node.layer] - layerMax[node.layer] / 2);
524 positions.push_back(NodePos{ImVec2(xpos, ypos)});
525 layerEntries[node.layer] += 1;
526 }
527 };
528
529 if (!inited) {
530 prepareChannelView(nodes, groups);
531 inited = true;
532 }
533
534 // Create our child canvas
535 ImGui::BeginGroup();
536 ImGui::Checkbox("Show grid", &show_grid);
537 ImGui::SameLine();
538 ImGui::Checkbox("Show legend", &show_legend);
539 ImGui::SameLine();
540 ImGui::Checkbox("Light mode", &state.topologyLightMode);
541 ImGui::SameLine();
542 if (ImGui::Button("Center")) {
543 scrolling = ImVec2(0., 0.);
544 }
545 ImGui::SameLine();
546 if (state.leftPaneVisible == false && ImGui::Button("Show tree")) {
547 state.leftPaneVisible = true;
548 }
549 if (state.leftPaneVisible == true && ImGui::Button("Hide tree")) {
550 state.leftPaneVisible = false;
551 }
552 ImGui::SameLine();
553 if (state.bottomPaneVisible == false && ImGui::Button("Show metrics")) {
554 state.bottomPaneVisible = true;
555 }
556 if (state.bottomPaneVisible == true && ImGui::Button("Hide metrics")) {
557 state.bottomPaneVisible = false;
558 }
559 ImGui::SameLine();
560 if (state.rightPaneVisible == false && ImGui::Button("Show inspector")) {
561 state.rightPaneVisible = true;
562 }
563 if (state.rightPaneVisible == true && ImGui::Button("Hide inspector")) {
564 state.rightPaneVisible = false;
565 }
566 ImGui::Separator();
567 ImGui::EndGroup();
568 auto toolbarSize = ImGui::GetItemRectSize();
569 // Draw a list of nodes on the left side
570 bool open_context_menu = false;
571 int node_hovered_in_list = -1;
572 int node_hovered_in_scene = -1;
573 if (state.leftPaneVisible) {
574 ImGui::BeginChild("node_list", ImVec2(state.leftPaneSize, 0));
575 ImGui::Text("Workflows %d", groups.Size);
576 ImGui::Separator();
577 for (int groupId = 0; groupId < groups.Size; groupId++) {
578 Group* group = &groups[groupId];
579 if (ImGui::TreeNodeEx(group->name, ImGuiTreeNodeFlags_DefaultOpen)) {
580 for (int node_idx = 0; node_idx < nodes.Size; node_idx++) {
581 Node* node = &nodes[node_idx];
582 if (node->GroupID != groupId) {
583 continue;
584 }
585 ImGui::Indent(15);
586 ImGui::PushID(node->ID);
587 if (ImGui::Selectable(node->Name, node->ID == node_selected)) {
588 if (ImGui::IsMouseDoubleClicked(0)) {
589 controls[node_selected].logVisible = true;
590 }
591 node_selected = node->ID;
592 }
593 if (ImGui::IsItemHovered()) {
594 node_hovered_in_list = node->ID;
595 open_context_menu |= ImGui::IsMouseClicked(1);
596 }
597 ImGui::PopID();
598 ImGui::Unindent(15);
599 }
600 ImGui::TreePop();
601 }
602 }
603 ImGui::EndChild();
604 ImGui::SameLine();
605 }
606
607 ImGui::BeginGroup();
608
609 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(1, 1));
610 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
611 auto canvasBg = state.topologyLightMode ? (ImU32)ImColor(250, 250, 252, 255) : (ImU32)ImColor(44, 44, 46, 255);
612 auto canvasText = (ImU32)ImColor(235, 235, 245, 255); // nodes are always dark, so text is always light
613 ImGui::PushStyleColor(ImGuiCol_ChildBg, canvasBg);
614 ImGui::PushStyleColor(ImGuiCol_Text, canvasText);
615 ImVec2 graphSize = ImGui::GetWindowSize();
616 if (state.leftPaneVisible) {
617 graphSize.x -= state.leftPaneSize;
618 }
619 if (state.rightPaneVisible) {
620 graphSize.x -= state.rightPaneSize;
621 }
622 graphSize.y -= toolbarSize.y + 20;
623 ImGui::BeginChild("scrolling_region", graphSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollWithMouse);
624 ImGui::PushItemWidth(graphSize.x);
625
626 ImVec2 offset = ImGui::GetCursorScreenPos() - scrolling;
627 ImDrawList* draw_list = ImGui::GetWindowDrawList();
628 // Number of layers we need. 2 per node, plus 2 for
629 // the background stuff.
630 draw_list->ChannelsSplit((nodes.Size + 2) * 2);
631
632 // Display grid
633 displayGrid(show_grid, offset, draw_list);
634
635 ImVec2 win_pos = ImGui::GetCursorScreenPos();
636 ImVec2 canvas_sz = ImGui::GetWindowSize();
637
638 // Arrow colors — richer amber in light mode to stand out on white canvas
639 const ImColor arrowBgColor = state.topologyLightMode ? ImColor(140, 80, 0) : ARROW_BACKGROUND_COLOR;
640 const ImColor arrowHalfColor = state.topologyLightMode ? ImColor(180, 110, 0) : ARROW_HALFGROUND_COLOR;
641 const ImColor arrowColor = state.topologyLightMode ? ImColor(220, 140, 0) : ARROW_COLOR;
642
643 // Display links but only if they are inside the view.
644 for (int link_idx = 0; link_idx < links.Size; link_idx++) {
645 // Do the geometry culling upfront.
646 NodeLink const& link = links[link_idx];
647 ImVec2 p1 = offset + NodePos::GetOutputSlotPos(nodes, positions, link.InputIdx, link.InputSlot);
648 ImVec2 p2 = ImVec2(-3 * NODE_SLOT_RADIUS, 0) + offset + NodePos::GetInputSlotPos(nodes, positions, link.OutputIdx, link.OutputSlot);
649
650 if ((p1.x > win_pos.x + canvas_sz.x + 50) && (p2.x > win_pos.x + canvas_sz.x + 50)) {
651 continue;
652 }
653
654 if ((p1.y > win_pos.y + canvas_sz.y + 50) && (p2.y > win_pos.y + canvas_sz.y + 50)) {
655 continue;
656 }
657
658 if ((p1.y < win_pos.y) && (p2.y < win_pos.y)) {
659 continue;
660 }
661
662 if ((p1.x < win_pos.x) && (p2.x < win_pos.x)) {
663 continue;
664 }
665 draw_list->ChannelsSetCurrent(0); // Background
666 auto color = arrowBgColor;
667 auto thickness = ARROW_BACKGROUND_THICKNESS;
668
669 bool p1Inside = false;
670 bool p2Inside = false;
671 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)) {
672 p1Inside = true;
673 }
674
675 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)) {
676 p2Inside = true;
677 }
678
679 if (p1Inside && p2Inside) {
680 // Whatever the two edges completely within the view, gets brighter color and foreground.
681 draw_list->ChannelsSetCurrent(2);
682 color = arrowColor;
683 thickness = ARROW_THICKNESS;
684 } else if (p1Inside || p2Inside) {
685 draw_list->ChannelsSetCurrent(1);
686 // Whenever one of the two ends is within the view, increase the color but keep the background
687 color = arrowHalfColor;
688 thickness = ARROW_HALFGROUND_THICKNESS;
689 }
690
691 // If something belongs to a selected node, it gets the selected color.
692 if (link.InputIdx == node_selected || link.OutputIdx == node_selected) {
693 auto foregroundLayer = (nodes.Size + 1) * 2 + 1;
694 draw_list->ChannelsSetCurrent(foregroundLayer);
695 color = ARROW_SELECTED_COLOR;
696 thickness = thickness + 2;
697 }
698
699 draw_list->AddBezierCurve(p1, p1 + ImVec2(+50, 0), p2 + ImVec2(-50, 0), p2, color, thickness);
700 }
701
702 auto fgDrawList = ImGui::GetForegroundDrawList();
703 // Display nodes
704 for (int node_idx = 0; node_idx < nodes.Size; node_idx++) {
705 auto backgroundLayer = (node_idx + 1) * 2;
706 auto foregroundLayer = (node_idx + 1) * 2 + 1;
707 // Selected node goes to front
708 if (node_selected == node_idx) {
709 backgroundLayer = (nodes.Size + 1) * 2;
710 foregroundLayer = (nodes.Size + 1) * 2 + 1;
711 }
712 Node* node = &nodes[node_idx];
713 NodePos* pos = &positions[node_idx];
714 const DeviceInfo& info = infos[node_idx];
715
716 ImVec2 node_rect_min = offset + pos->pos;
717
718 // Do not even start if we are sure the box is not visible
719 if ((node_rect_min.x > ImGui::GetCursorScreenPos().x + ImGui::GetWindowSize().x + 50) ||
720 (node_rect_min.y > ImGui::GetCursorScreenPos().y + ImGui::GetWindowSize().y + 50)) {
721 continue;
722 }
723
724 ImGui::PushID(node->ID);
725
726 // Display node contents first
727 draw_list->ChannelsSetCurrent(foregroundLayer);
728 bool old_any_active = ImGui::IsAnyItemActive();
729 ImGui::SetCursorScreenPos(node_rect_min + NODE_WINDOW_PADDING);
730 ImGui::BeginGroup(); // Lock horizontal position
731 ImGui::TextUnformatted(node->Name);
732 switch (info.maxLogLevel) {
733 case LogLevel::Critical:
734 ImGui::SameLine();
735 ImGui::TextColored(ERROR_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_CIRCLE);
736 break;
737 case LogLevel::Error:
738 ImGui::SameLine();
739 ImGui::TextColored(ERROR_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_CIRCLE);
740 break;
741 case LogLevel::Alarm:
742 ImGui::SameLine();
743 ImGui::TextColored(WARNING_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_TRIANGLE);
744 break;
745 case LogLevel::Warning:
746 ImGui::SameLine();
747 ImGui::TextColored(WARNING_MESSAGE_COLOR, "%s", ICON_FA_EXCLAMATION_TRIANGLE);
748 break;
749 default:
750 break;
751 }
752
753 gui::displayDataRelayer(metricsInfos[node->ID], infos[node->ID], specs[node->ID], allStates[node->ID], ImVec2(200., 160.), controls[node->ID].firstWnd);
754 ImGui::EndGroup();
755
756 // Save the size of what we have emitted and whether any of the widgets are being used
757 bool node_widgets_active = (!old_any_active && ImGui::IsAnyItemActive());
758 float attemptX = std::max(ImGui::GetItemRectSize().x, 150.f);
759 float attemptY = std::min(ImGui::GetItemRectSize().y, 128.f);
760 node->Size = ImVec2(attemptX, attemptY) + NODE_WINDOW_PADDING + NODE_WINDOW_PADDING;
761 ImVec2 node_rect_max = node_rect_min + node->Size;
762 ImVec2 node_rect_title = node_rect_min + ImVec2(node->Size.x, 24);
763
764 if (node_rect_min.x > 20 + 2 * NODE_WINDOW_PADDING.x + state.leftPaneSize + graphSize.x) {
765 ImGui::PopID();
766 continue;
767 }
768 if (node_rect_min.y > 20 + 2 * NODE_WINDOW_PADDING.y + toolbarSize.y + graphSize.y) {
769 ImGui::PopID();
770 continue;
771 }
772
773 // Display node box
774 draw_list->ChannelsSetCurrent(backgroundLayer); // Background
775 ImGui::SetCursorScreenPos(node_rect_min);
776 ImGui::InvisibleButton("node", node->Size);
777 if (ImGui::IsItemHovered()) {
778 node_hovered_in_scene = node->ID;
779 open_context_menu |= ImGui::IsMouseClicked(1);
780 if (ImGui::IsMouseDoubleClicked(0)) {
781 controls[node->ID].logVisible = true;
782 }
783 }
784 bool node_moving_active = ImGui::IsItemActive();
785 if (node_widgets_active || node_moving_active) {
786 node_selected = node->ID;
787 }
788 if (node_moving_active && ImGui::IsMouseDragging(0)) {
789 pos->pos = pos->pos + ImGui::GetIO().MouseDelta;
790 }
791 if (ImGui::IsWindowHovered() && !node_moving_active && ImGui::IsMouseDragging(0)) {
792 scrolling = scrolling - ImVec2(ImGui::GetIO().MouseDelta.x / 4.f, ImGui::GetIO().MouseDelta.y / 4.f);
793 }
794
795 auto nodeBg = decideColorForNode(info, state.topologyLightMode);
796
797 auto hovered = (node_hovered_in_list == node->ID || node_hovered_in_scene == node->ID || (node_hovered_in_list == -1 && node_selected == node->ID));
798 ImVec4 nodeBgColor = hovered ? nodeBg.hovered : nodeBg.normal;
799 ImVec4 nodeTitleColor = hovered ? nodeBg.title_hovered : nodeBg.title;
800 ImU32 node_bg_color = ImGui::ColorConvertFloat4ToU32(nodeBgColor);
801 ImU32 node_title_color = ImGui::ColorConvertFloat4ToU32(nodeTitleColor);
802
803 draw_list->AddRectFilled(node_rect_min + ImVec2(3.f, 3.f), node_rect_max + ImVec2(3.f, 3.f), ImColor(0, 0, 0, 70), 4.0f);
804 draw_list->AddRectFilled(node_rect_min, node_rect_max, node_bg_color, 4.0f);
805 draw_list->AddRectFilled(node_rect_min, node_rect_title, node_title_color, 4.0f);
806 draw_list->AddRect(node_rect_min, node_rect_max, NODE_BORDER_COLOR, NODE_BORDER_THICKNESS);
807
808 for (int slot_idx = 0; slot_idx < node->InputsCount; slot_idx++) {
809 draw_list->ChannelsSetCurrent(backgroundLayer); // Background
811 auto slotPos = NodePos::GetInputSlotPos(nodes, positions, node_idx, slot_idx);
812 auto pp1 = p1 + offset + slotPos;
813 auto pp2 = p2 + offset + slotPos;
814 auto pp3 = p3 + offset + slotPos;
815 auto color = arrowColor;
816 if (node_idx == node_selected) {
817 color = ARROW_SELECTED_COLOR;
818 }
819 draw_list->AddTriangleFilled(pp1, pp2, pp3, color);
820 draw_list->AddCircleFilled(offset + slotPos, NODE_SLOT_RADIUS, INPUT_SLOT_COLOR);
821 }
822
823 draw_list->ChannelsSetCurrent(foregroundLayer);
824 MetricLabelsContext context{&nodes, node_idx, &positions, draw_list, offset};
827 "input_labels",
828 offset,
829 ImVec2{node->Size.x, node->Size.y},
830 context,
833 MetricsPainter<int, uint64_t, MetricLabelsContext>::items2D(info.inputChannelMetricsViewIndex),
834 MetricsPainter<int, uint64_t, MetricLabelsContext>::latestMetric<uint64_t>(metricsInfos[node->ID], info.inputChannelMetricsViewIndex),
836 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),
837 [](int, int item, int value, ImU32 color, MetricLabelsContext const& context) {
838 auto draw_list = context.draw_list;
839 auto offset = context.offset;
840 auto slotPos = NodePos::GetInputSlotPos(nodes, positions, context.nodeIdx, item);
841 Node* node = &nodes[context.nodeIdx];
842 auto& label = node->oldestPossibleInput[item];
843 // Avoid recomputing if the value is the same.
844 if (label.value != value) {
845 label.value = value;
846 snprintf(label.buffer, sizeof(label.buffer), "%d", value);
847 label.textSize = ImGui::CalcTextSize(label.buffer).x;
848 }
849 draw_list->AddRectFilled(offset + slotPos - ImVec2{node->oldestPossibleInput[item].textSize + 5.f * NODE_SLOT_RADIUS, 2 * NODE_SLOT_RADIUS},
850 offset + slotPos + ImVec2{-4.5f * NODE_SLOT_RADIUS, 2 * NODE_SLOT_RADIUS}, NODE_LABEL_BACKGROUND_COLOR, 2., ImDrawFlags_RoundCornersAll);
851 draw_list->AddText(nullptr, 12,
852 offset + slotPos - ImVec2{node->oldestPossibleInput[item].textSize + 4.5f * NODE_SLOT_RADIUS, 2 * NODE_SLOT_RADIUS},
853 NODE_LABEL_TEXT_COLOR,
854 node->oldestPossibleInput[item].buffer);
855 });
856
857 for (int slot_idx = 0; slot_idx < node->OutputsCount; slot_idx++) {
858 draw_list->AddCircleFilled(offset + NodePos::GetOutputSlotPos(nodes, positions, node_idx, slot_idx), NODE_SLOT_RADIUS, OUTPUT_SLOT_COLOR);
859 }
860
862 "output_labels",
863 offset,
864 ImVec2{node->Size.x, node->Size.y},
865 context,
868 MetricsPainter<int, uint64_t, MetricLabelsContext>::items2D(info.outputChannelMetricsViewIndex),
869 MetricsPainter<int, uint64_t, MetricLabelsContext>::latestMetric<uint64_t>(metricsInfos[node->ID], info.outputChannelMetricsViewIndex),
871 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),
872 [](int, int item, int value, ImU32 color, MetricLabelsContext const& context) {
873 auto draw_list = context.draw_list;
874 auto offset = context.offset;
875 auto slotPos = NodePos::GetOutputSlotPos(nodes, positions, context.nodeIdx, item);
876 Node* node = &nodes[context.nodeIdx];
877 auto& label = node->oldestPossibleOutput[item];
878 // Avoid recomputing if the value is the same.
879 if (label.value != value) {
880 label.value = value;
881 snprintf(label.buffer, sizeof(label.buffer), "%d", value);
882 label.textSize = ImGui::CalcTextSize(label.buffer).x;
883 }
884 auto rectTL = ImVec2{4.5f * NODE_SLOT_RADIUS, -2 * NODE_SLOT_RADIUS};
885 auto rectBR = ImVec2{node->oldestPossibleOutput[item].textSize + 5.f * NODE_SLOT_RADIUS, 2 * NODE_SLOT_RADIUS};
886 draw_list->AddRectFilled(offset + slotPos + rectTL,
887 offset + slotPos + rectBR, NODE_LABEL_BACKGROUND_COLOR, 2., ImDrawFlags_RoundCornersAll);
888 draw_list->AddText(nullptr, 12,
889 offset + slotPos + rectTL,
890 NODE_LABEL_TEXT_COLOR,
891 node->oldestPossibleOutput[item].buffer);
892 });
893
894 ImGui::PopID();
895 }
896 draw_list->ChannelsMerge();
897 displayLegend(show_legend, offset, draw_list);
898
899 // Open context menu
900 if (!ImGui::IsAnyItemHovered() && ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow) && ImGui::IsMouseClicked(1)) {
901 node_selected = node_hovered_in_list = node_hovered_in_scene = -1;
902 open_context_menu = true;
903 }
904 if (open_context_menu) {
905 ImGui::OpenPopup("context_menu");
906 if (node_hovered_in_list != -1) {
907 node_selected = node_hovered_in_list;
908 }
909 if (node_hovered_in_scene != -1) {
910 node_selected = node_hovered_in_scene;
911 }
912 }
913
914 // Scrolling
915 // if (ImGui::IsWindowHovered() && !ImGui::IsAnyItemActive() && ImGui::IsMouseDragging(2, 0.0f))
916 // scrolling = scrolling - ImGui::GetIO().MouseDelta;
917
918 ImGui::PopItemWidth();
919 ImGui::EndChild();
920 ImGui::PopStyleColor(2);
921 ImGui::PopStyleVar(2);
922 ImGui::EndGroup();
923
924 if (state.rightPaneVisible) {
925 ImGui::SameLine();
926 ImGui::BeginGroup();
927 ImGui::BeginChild("inspector");
928 ImGui::TextUnformatted("Device Inspector");
929 ImGui::Separator();
930 if (node_selected != -1) {
931 auto& node = nodes[node_selected];
932 auto& spec = specs[node_selected];
933 auto& states = allStates[node_selected];
934 auto& control = controls[node_selected];
935 auto& info = infos[node_selected];
936 auto& metrics = metricsInfos[node_selected];
937 auto& group = groups[node.GroupID];
938 auto& metadatum = metadata[group.metadataId];
939
940 if (state.rightPaneVisible) {
941 gui::displayDeviceInspector(spec, info, states, metrics, metadatum, control);
942 }
943 } else {
944 ImGui::TextWrapped("Select a node in the topology to display information about it");
945 }
946 ImGui::EndChild();
947 ImGui::EndGroup();
948 }
949 ImGui::End();
950}
951
952} // namespace o2::framework::gui
953#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
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
GLsizei GLuint * groups
Definition glcorearb.h:3984
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
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)
NodeColor decideColorForNode(const DeviceInfo &info, bool lightMode)
const float NODE_SLOT_RADIUS
Node size.
void displayLegend(bool show_legend, ImVec2 offset, ImDrawList *draw_list)
void displayGrid(bool show_grid, ImVec2 offset, ImDrawList *draw_list)
Displays a grid.
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.
const ImVec2 NODE_WINDOW_PADDING(8.0f, 8.0f)
@ EndOfStreaming
End of streaming requested, but not notified.
@ Streaming
Data is being processed.
@ Idle
End of streaming notified.
D const SVectorGPU< T, D > & rhs
Definition SMatrixGPU.h:193
Vertex< T > operator-(const Vertex< T > &a, const Vertex< T > &b)
Definition Vertex.h:98
BinCenterView< AxisIterator > operator+(BinCenterView< AxisIterator > lhs, int n)
double getValue(DPVAL dp)
LogLevel
Possible log levels for device log entries.
int w
The size in X of the metrics.
std::vector< std::size_t > indexes
The row major list of indices for the metrics which compose the 2D view.
int h
The size in Y of the metrics.
bool isComplete() const
Whether or not the view is ready to be used.
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
Helper struct to keep track of the results of the topological sort.
static std::vector< TopoIndexInfo > topologicalSort(size_t nodeCount, int const *edgeIn, int const *edgeOut, size_t byteStride, size_t edgesCount)
Group(int id, char const *n, size_t mid)
Context to draw the labels with the metrics.
std::function< ImU32(int value)> ColorCallback
std::function< void(int row, int column, int value, ImU32 color, CONTEXT const &context)> PaintCallback
std::function< size_t(void)> NumRecordsCallback
std::function< int(ITEM const &)> ValueCallback
std::function< size_t(RECORD const &)> NumItemsCallback
static ColorCallback colorPalette(std::vector< ImU32 > const &colors, int minValue, int maxValue)
static NumRecordsCallback metric2D(Metric2DViewIndex &viewIndex)
static ItemCallback latestMetric(DeviceMetricsInfo const &metrics, Metric2DViewIndex const &viewIndex)
std::function< RECORD(size_t)> RecordCallback
std::function< ITEM const &(RECORD const &, size_t)> ItemCallback
static NumItemsCallback items2D(Metric2DViewIndex const &viewIndex)
static void draw(const char *name, ImVec2 const &offset, ImVec2 const &sizeHint, CONTEXT const &context, NumRecordsCallback const &getNumRecords, RecordCallback const &getRecord, NumItemsCallback const &getNumItems, ItemCallback const &getItem, ValueCallback const &getValue, ColorCallback const &getColor, PaintCallback const &describeCell)
static ImVec2 GetInputSlotPos(ImVector< Node > const &infos, ImVector< NodePos > const &positions, int nodeId, int slot_no)
static ImVec2 GetOutputSlotPos(ImVector< Node > const &infos, ImVector< NodePos > const &positions, int nodeId, int slot_no)
Node(int id, int groupID, char const *name, float value, const ImVec4 &color, int inputs_count, int outputs_count)
OldestPossibleOutput oldestPossibleOutput[MAX_SLOTS]
OldestPossibleInput oldestPossibleInput[MAX_SLOTS]
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::vector< int > row