Skip to content

Commit 5ac37dc

Browse files
committed
DPL GUI: layout device graph using topological sort
This provides a somewhat nicer layout, by layering data processors according to the pass in the topological sort they belong to.
1 parent 275a5b2 commit 5ac37dc

File tree

4 files changed

+255
-104
lines changed

4 files changed

+255
-104
lines changed

Framework/Core/src/FrameworkGUIDevicesGraph.cxx

Lines changed: 84 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "Framework/LogParsingHelpers.h"
1616
#include "Framework/PaletteHelpers.h"
1717
#include "FrameworkGUIDeviceInspector.h"
18+
#include "../src/WorkflowHelpers.h"
1819
#include "DebugGUI/imgui.h"
1920
#include <algorithm>
2021
#include <cmath>
@@ -95,6 +96,59 @@ void displayGrid(bool show_grid, ImVec2 offset, ImDrawList* draw_list)
9596
}
9697
}
9798

99+
// Private helper struct for the graph model
100+
struct Node {
101+
int ID;
102+
char Name[32];
103+
ImVec2 Size;
104+
float Value;
105+
ImVec4 Color;
106+
int InputsCount, OutputsCount;
107+
108+
Node(int id, const char* name, float value, const ImVec4& color, int inputs_count, int outputs_count)
109+
{
110+
ID = id;
111+
strncpy(Name, name, 31);
112+
Name[31] = 0;
113+
Value = value;
114+
Color = color;
115+
InputsCount = inputs_count;
116+
OutputsCount = outputs_count;
117+
}
118+
};
119+
120+
// Private helper struct for the layout of the graph
121+
struct NodePos {
122+
ImVec2 pos;
123+
static ImVec2 GetInputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
124+
{
125+
ImVec2 const& pos = positions[nodeId].pos;
126+
ImVec2 const& size = infos[nodeId].Size;
127+
float inputsCount = infos[nodeId].InputsCount;
128+
return ImVec2(pos.x, pos.y + size.y * ((float)slot_no + 1) / (inputsCount + 1));
129+
}
130+
static ImVec2 GetOutputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
131+
{
132+
ImVec2 const& pos = positions[nodeId].pos;
133+
ImVec2 const& size = infos[nodeId].Size;
134+
float outputsCount = infos[nodeId].OutputsCount;
135+
return ImVec2(pos.x + size.x, pos.y + size.y * ((float)slot_no + 1) / (outputsCount + 1));
136+
}
137+
};
138+
139+
// Private helper struct for the edges in the graph
140+
struct NodeLink {
141+
int InputIdx, InputSlot, OutputIdx, OutputSlot;
142+
143+
NodeLink(int input_idx, int input_slot, int output_idx, int output_slot)
144+
{
145+
InputIdx = input_idx;
146+
InputSlot = input_slot;
147+
OutputIdx = output_idx;
148+
OutputSlot = output_slot;
149+
}
150+
};
151+
98152
void showTopologyNodeGraph(WorkspaceGUIState& state,
99153
const std::vector<DeviceInfo>& infos,
100154
const std::vector<DeviceSpec>& specs,
@@ -110,58 +164,6 @@ void showTopologyNodeGraph(WorkspaceGUIState& state,
110164

111165
ImGui::Begin("Physical topology view", nullptr, ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize);
112166

113-
// Dummy
114-
struct Node {
115-
int ID;
116-
char Name[32];
117-
ImVec2 Size;
118-
float Value;
119-
ImVec4 Color;
120-
int InputsCount, OutputsCount;
121-
122-
Node(int id, const char* name, float value, const ImVec4& color, int inputs_count, int outputs_count)
123-
{
124-
ID = id;
125-
strncpy(Name, name, 31);
126-
Name[31] = 0;
127-
Value = value;
128-
Color = color;
129-
InputsCount = inputs_count;
130-
OutputsCount = outputs_count;
131-
}
132-
133-
};
134-
135-
struct NodePos {
136-
ImVec2 pos;
137-
static ImVec2 GetInputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
138-
{
139-
ImVec2 const& pos = positions[nodeId].pos;
140-
ImVec2 const& size = infos[nodeId].Size;
141-
float inputsCount = infos[nodeId].InputsCount;
142-
return ImVec2(pos.x, pos.y + size.y * ((float)slot_no + 1) / (inputsCount + 1));
143-
}
144-
static ImVec2 GetOutputSlotPos(ImVector<Node> const& infos, ImVector<NodePos> const& positions, int nodeId, int slot_no)
145-
{
146-
ImVec2 const& pos = positions[nodeId].pos;
147-
ImVec2 const& size = infos[nodeId].Size;
148-
float outputsCount = infos[nodeId].OutputsCount;
149-
return ImVec2(pos.x + size.x, pos.y + size.y * ((float)slot_no + 1) / (outputsCount + 1));
150-
}
151-
};
152-
153-
struct NodeLink {
154-
int InputIdx, InputSlot, OutputIdx, OutputSlot;
155-
156-
NodeLink(int input_idx, int input_slot, int output_idx, int output_slot)
157-
{
158-
InputIdx = input_idx;
159-
InputSlot = input_slot;
160-
OutputIdx = output_idx;
161-
OutputSlot = output_slot;
162-
}
163-
};
164-
165167
static ImVector<Node> nodes;
166168
static ImVector<NodeLink> links;
167169
static ImVector<NodePos> positions;
@@ -180,18 +182,17 @@ void showTopologyNodeGraph(WorkspaceGUIState& state,
180182
for (int si = 0; si < specs.size(); ++si) {
181183
int oi = 0;
182184
for (auto&& output : specs[si].outputChannels) {
183-
linkToIndex.insert(std::make_pair(output.name, LinkInfo{si, oi}));
185+
linkToIndex.insert(std::make_pair(output.name, LinkInfo{ si, oi }));
184186
oi += 1;
185187
}
186188
}
189+
// Do matching between inputs and outputs
187190
for (int si = 0; si < specs.size(); ++si) {
188191
auto& spec = specs[si];
189-
// FIXME: display nodes using topological sort
190192
nodeList.push_back(Node(si, spec.id.c_str(), 0.5f,
191193
ImColor(255, 100, 100),
192194
spec.inputChannels.size(),
193195
spec.outputChannels.size()));
194-
positions.push_back(NodePos{ImVec2(40 + 120 * si, 50 + (120 * si) % 500)});
195196
int ii = 0;
196197
for (auto& input : spec.inputChannels) {
197198
auto const& outName = input.name;
@@ -204,6 +205,34 @@ void showTopologyNodeGraph(WorkspaceGUIState& state,
204205
ii += 1;
205206
}
206207
}
208+
209+
// ImVector does boudary checks, so I bypass the case there is no
210+
// edges.
211+
std::vector<TopoIndexInfo> sortedNodes = { { 0, 0 } };
212+
if (links.size()) {
213+
sortedNodes = WorkflowHelpers::topologicalSort(specs.size(), &(links[0].InputIdx), &(links[0].OutputIdx), sizeof(links[0]), links.size());
214+
}
215+
/// We resort them again, this time with the added layer information
216+
std::sort(sortedNodes.begin(), sortedNodes.end());
217+
218+
std::vector<int> layerEntries(1024, 0);
219+
std::vector<int> layerMax(1024, 0);
220+
for (auto& node : sortedNodes) {
221+
layerMax[node.layer < 1023 ? node.layer : 1023] += 1;
222+
}
223+
224+
assert(specs.size() == sortedNodes.size());
225+
// FIXME: display nodes using topological sort
226+
// Update positions
227+
for (int si = 0; si < specs.size(); ++si) {
228+
auto& node = sortedNodes[si];
229+
assert(node.index == si);
230+
int xpos = 40 + 240 * node.layer;
231+
int ypos = 300 + (600 / (layerMax[node.layer] + 1)) * (layerEntries[node.layer] - layerMax[node.layer] / 2);
232+
positions.push_back(NodePos{ ImVec2(xpos, ypos) });
233+
layerEntries[node.layer] += 1;
234+
}
235+
207236
};
208237

209238
if (!inited) {

Framework/Core/src/WorkflowHelpers.cxx

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,26 @@ namespace o2
2424
namespace framework
2525
{
2626

27-
std::vector<size_t>
28-
WorkflowHelpers::topologicalSort(size_t nodeCount,
29-
const size_t *edgeIn,
30-
const size_t *edgeOut,
31-
size_t stride,
32-
size_t edgesCount) {
33-
using NodeIndex = size_t;
34-
using EdgeIndex = size_t;
27+
std::ostream& operator<<(std::ostream& out, TopoIndexInfo const& info)
28+
{
29+
out << "(" << info.index << ", " << info.layer << ")";
30+
return out;
31+
}
32+
33+
std::vector<TopoIndexInfo>
34+
WorkflowHelpers::topologicalSort(size_t nodeCount,
35+
int const* edgeIn,
36+
int const* edgeOut,
37+
size_t byteStride,
38+
size_t edgesCount)
39+
{
40+
size_t stride = byteStride / sizeof(int);
41+
using EdgeIndex = int;
3542
// Create the index which will be returned.
36-
std::vector<NodeIndex> index(nodeCount);
37-
for (NodeIndex wi = 0; wi < nodeCount; ++wi) {
38-
index[wi] = wi;
43+
std::vector<TopoIndexInfo> index(nodeCount);
44+
for (int wi = 0; wi < nodeCount; ++wi) {
45+
index[wi] = { wi, 0 };
3946
}
40-
// Temporary vector holding vertices to be processed
4147
std::vector<EdgeIndex> remainingEdgesIndex(edgesCount);
4248
for (EdgeIndex ei = 0; ei < edgesCount; ++ei) {
4349
remainingEdgesIndex[ei] = ei;
@@ -47,44 +53,55 @@ WorkflowHelpers::topologicalSort(size_t nodeCount,
4753
// if the vector has dependencies, false otherwise
4854
std::vector<bool> nodeDeps(nodeCount, false);
4955
for (EdgeIndex ei = 0; ei < edgesCount; ++ei) {
50-
nodeDeps[*(edgeOut+ei*stride)] = true;
56+
nodeDeps[*(edgeOut + ei * stride)] = true;
5157
}
5258

53-
std::list<NodeIndex> L;
54-
std::vector<NodeIndex> S;
55-
std::set<NodeIndex> nextVertex;
56-
std::vector<EdgeIndex> nextEdges;
57-
58-
for (size_t ii = 0, ie = index.size(); ii < ie; ++ii) {
59+
// We start with all those which do not have any dependencies
60+
// They are layer 0.
61+
std::list<TopoIndexInfo> L;
62+
for (int ii = 0; ii < index.size(); ++ii) {
5963
if (nodeDeps[ii] == false) {
60-
L.push_back(ii);
64+
L.push_back({ ii, 0 });
6165
}
6266
}
67+
68+
// The final result.
69+
std::vector<TopoIndexInfo> S;
70+
// The set of vertices which can be reached by the current node
71+
std::set<TopoIndexInfo> nextVertex;
72+
// The set of edges which are not related to the current node.
73+
std::vector<EdgeIndex> nextEdges;
6374
while (!L.empty()) {
6475
auto node = L.front();
6576
S.push_back(node);
6677
L.pop_front();
6778
nextVertex.clear();
6879
nextEdges.clear();
6980

70-
for (EdgeIndex ei = 0, ee = remainingEdgesIndex.size(); ei < ee; ++ei) {
71-
if (*(edgeIn+ei*stride) == node) {
72-
nextVertex.insert(*(edgeOut+ei*stride));
81+
// After this, nextVertex will contain all the vertices
82+
// which have the current node as incoming.
83+
// nextEdges will contain all the edges which are not related
84+
// to the current node.
85+
for (auto& ei : remainingEdgesIndex) {
86+
if (*(edgeIn + ei * stride) == node.index) {
87+
nextVertex.insert({ *(edgeOut + ei * stride), node.layer + 1 });
7388
} else {
74-
nextEdges.push_back(remainingEdgesIndex[ei]);
89+
nextEdges.push_back(ei);
7590
}
7691
}
7792
remainingEdgesIndex.swap(nextEdges);
7893

79-
std::set<NodeIndex> hasPredecessors;
80-
for (auto &ei : remainingEdgesIndex) {
81-
for (auto &m : nextVertex) {
82-
if (m == *(edgeOut+ei*stride)) {
83-
hasPredecessors.insert(m);
94+
// Of all the vertices which have node as incoming,
95+
// check if there is any other incoming node.
96+
std::set<TopoIndexInfo> hasPredecessors;
97+
for (auto& ei : remainingEdgesIndex) {
98+
for (auto& m : nextVertex) {
99+
if (m.index == *(edgeOut + ei * stride)) {
100+
hasPredecessors.insert({ m.index, m.layer });
84101
}
85102
}
86103
}
87-
std::vector<NodeIndex> withPredecessor;
104+
std::vector<TopoIndexInfo> withPredecessor;
88105
std::set_difference(nextVertex.begin(), nextVertex.end(),
89106
hasPredecessors.begin(), hasPredecessors.end(),
90107
std::back_inserter(withPredecessor));

Framework/Core/src/WorkflowHelpers.h

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <cstddef>
1919
#include <vector>
20+
#include <iosfwd>
2021

2122
namespace o2
2223
{
@@ -102,22 +103,39 @@ struct EdgeAction {
102103
bool requiresNewChannel = false;
103104
};
104105

106+
/// Helper struct to keep track of the results of the topological sort
107+
struct TopoIndexInfo {
108+
int index; //!< the index in the actual storage of the nodes to be sorted topologically
109+
int layer; //!< the associated layer in the sorting procedure
110+
bool operator<(TopoIndexInfo const& rhs) const
111+
{
112+
return index < rhs.index;
113+
}
114+
bool operator==(TopoIndexInfo const& rhs) const
115+
{
116+
return index == rhs.index;
117+
}
118+
119+
friend std::ostream& operator<<(std::ostream& out, TopoIndexInfo const& info);
120+
};
121+
105122
/// A set of internal helper classes to manipulate a Workflow
106123
struct WorkflowHelpers {
107124
/// Topological sort for a graph of @a nodeCount nodes.
108-
///
125+
///
109126
/// @a edgeIn pointer to the index of the input node for the first edge
110127
/// @a edgeOut pointer to the index of the out node for the first edge
111-
/// @a stride distance (in size_t) between the first and the second element the array
128+
/// @a stride distance (in bytes) between the first and the second element the array
112129
/// holding the edges
113130
/// @return an index vector for the @a nodeCount nodes, where the order is a topological
114-
/// sort according to the information provided in edges.
115-
static std::vector<size_t> topologicalSort(size_t nodeCount,
116-
const size_t *edgeIn,
117-
const size_t *edgeOut,
118-
size_t stride,
119-
size_t edgesCount
120-
);
131+
/// sort according to the information provided in edges. The first element of
132+
/// the pair is the index in the nodes array, the second one is the layer in the topological
133+
/// sort.
134+
static std::vector<TopoIndexInfo> topologicalSort(size_t nodeCount,
135+
int const* edgeIn,
136+
int const* edgeOut,
137+
size_t stride,
138+
size_t edgesCount);
121139

122140
// Helper method to verify that a given workflow is actually valid e.g. that
123141
// it contains no empty labels.

0 commit comments

Comments
 (0)