Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
GraphAutoLayout.cpp
Go to the documentation of this file.
1/**
2 * @file GraphAutoLayout.cpp
3 * @brief Implementation of the hierarchical auto-layout algorithm (Phase 6).
4 * @author Olympe Engine
5 * @date 2026-03-09
6 *
7 * C++14 compliant — no std::optional, structured bindings, std::filesystem.
8 */
9
10#include "GraphAutoLayout.h"
11
12#include <algorithm>
13#include <queue>
14#include <unordered_map>
15#include <unordered_set>
16
17namespace Olympe {
18
19// ============================================================================
20// SetPos helper
21// ============================================================================
22
24{
27 bx.LiteralValue = TaskValue(x);
29 by.LiteralValue = TaskValue(y);
30 node.Parameters["__posX"] = bx;
31 node.Parameters["__posY"] = by;
32}
33
34// ============================================================================
35// BuildLayers
36// ============================================================================
37
38std::vector<GraphAutoLayout::Layer> GraphAutoLayout::BuildLayers(
40{
41 std::vector<Layer> layers;
42 if (graph.Nodes.empty()) return layers;
43
44 // Determine root node: prefer EntryPoint, fall back to first node
46 if (graph.EntryPointID != NODE_INDEX_NONE)
47 {
48 rootID = graph.EntryPointID;
49 }
50 else
51 {
52 // Walk nodes to find one with type EntryPoint
53 for (const auto& n : graph.Nodes)
54 {
55 if (n.Type == TaskNodeType::EntryPoint)
56 {
57 rootID = n.NodeID;
58 break;
59 }
60 }
62 {
63 rootID = graph.Nodes.front().NodeID;
64 }
65 }
66
67 // Build adjacency list from ExecConnections
68 std::unordered_map<int32_t, std::vector<int32_t>> adj;
69 for (const auto& n : graph.Nodes) adj[n.NodeID]; // ensure all keys exist
70 for (const auto& ec : graph.ExecConnections)
71 {
72 if (ec.SourceNodeID != NODE_INDEX_NONE && ec.TargetNodeID != NODE_INDEX_NONE)
73 adj[ec.SourceNodeID].push_back(ec.TargetNodeID);
74 }
75
76 // BFS to assign layers (by shortest path from root)
77 std::unordered_map<int32_t, int> layerOf;
78 std::queue<int32_t> bfsQueue;
79
80 layerOf[rootID] = 0;
81 bfsQueue.push(rootID);
82
83 while (!bfsQueue.empty())
84 {
85 int32_t cur = bfsQueue.front();
86 bfsQueue.pop();
87
88 int curLayer = layerOf[cur];
89
90 for (int32_t child : adj[cur])
91 {
92 if (layerOf.find(child) == layerOf.end())
93 {
94 layerOf[child] = curLayer + 1;
95 bfsQueue.push(child);
96 }
97 }
98 }
99
100 // Collect all layer indices
101 int maxLayer = 0;
102 for (const auto& kv : layerOf)
103 if (kv.second > maxLayer) maxLayer = kv.second;
104
105 layers.resize(static_cast<std::size_t>(maxLayer + 1));
106 for (const auto& kv : layerOf)
107 {
108 layers[static_cast<std::size_t>(kv.second)].NodeIDs.push_back(kv.first);
109 }
110
111 // Collect orphan nodes (not reached by BFS)
112 std::unordered_set<int32_t> visited;
113 for (const auto& kv : layerOf) visited.insert(kv.first);
114
116 for (const auto& n : graph.Nodes)
117 {
118 if (visited.find(n.NodeID) == visited.end())
119 orphanLayer.NodeIDs.push_back(n.NodeID);
120 }
121 if (!orphanLayer.NodeIDs.empty())
122 layers.push_back(orphanLayer);
123
124 // Sort nodes within each layer by NodeID for deterministic output
125 for (auto& layer : layers)
126 {
127 std::sort(layer.NodeIDs.begin(), layer.NodeIDs.end());
128 }
129
130 return layers;
131}
132
133// ============================================================================
134// AssignPositions
135// ============================================================================
136
138 const std::vector<Layer>& layers)
139{
140 if (graph.Nodes.empty() || layers.empty()) return;
141
142 // Build a fast mutable lookup (node ID → index in Nodes vector)
143 std::unordered_map<int32_t, std::size_t> indexOf;
144 for (std::size_t i = 0; i < graph.Nodes.size(); ++i)
145 indexOf[graph.Nodes[i].NodeID] = i;
146
147 for (std::size_t layerIdx = 0; layerIdx < layers.size(); ++layerIdx)
148 {
149 const Layer& layer = layers[layerIdx];
150 float x = ORIGIN_X + static_cast<float>(layerIdx) * SPACING_X;
151
152 for (std::size_t rowIdx = 0; rowIdx < layer.NodeIDs.size(); ++rowIdx)
153 {
154 int32_t nodeID = layer.NodeIDs[rowIdx];
155 float y = ORIGIN_Y + static_cast<float>(rowIdx) * SPACING_Y;
156
157 auto it = indexOf.find(nodeID);
158 if (it != indexOf.end())
159 {
160 SetPos(graph.Nodes[it->second], x, y);
161 }
162 }
163 }
164}
165
166// ============================================================================
167// ApplyHierarchicalLayout
168// ============================================================================
169
171{
172 std::vector<Layer> layers = BuildLayers(graph);
173 AssignPositions(graph, layers);
174}
175
176} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Hierarchical (Sugiyama-inspired) auto-layout for VS task graphs (Phase 6).
static constexpr float ORIGIN_X
X coordinate of the root (EntryPoint) column.
static constexpr float SPACING_Y
Vertical gap between rows (pixels).
static void AssignPositions(TaskGraphTemplate &graph, const std::vector< Layer > &layers)
Assigns x/y positions from the layer structure.
static void SetPos(TaskNodeDefinition &node, float x, float y)
Writes a float position into a node's Parameters map.
static constexpr float SPACING_X
Horizontal gap between columns (pixels).
static std::vector< Layer > BuildLayers(const TaskGraphTemplate &graph)
Builds layers via BFS from the entry point.
static constexpr float ORIGIN_Y
Y coordinate of the first row.
static void ApplyHierarchicalLayout(TaskGraphTemplate &graph)
Assigns screen-space positions to every node in graph.
Immutable, shareable task graph asset.
C++14-compliant type-safe value container for task parameters.
< Provides AssetID and INVALID_ASSET_ID
@ Literal
Value is embedded directly in the template.
@ EntryPoint
Unique entry node for VS graphs (replaces Root)
constexpr int32_t NODE_INDEX_NONE
Sentinel value for "no node" in node index / ID fields.
A single layer in the hierarchical layout.
std::vector< int32_t > NodeIDs
IDs of nodes assigned to this layer, in order.
Describes how a single parameter value is supplied to a task node.
ParameterBindingType Type
Binding mode.
Full description of a single node in the task graph.