Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
dag.cpp
Go to the documentation of this file.
1#include <pch.hpp>
2
4
6
7#include <algorithm>
8#include <cstddef>
9#include <expected>
10#include <format>
11#include <queue>
12#include <vector>
13
14namespace helios::ecs {
15
17 const size_t index = nodes_.size();
18 nodes_.push_back(
19 {.id = id, .kind = NodeKind::kSystem, .insertion_index = index});
20 node_ids_.push_back(id);
21 adjacency_.emplace_back();
22 id_to_index_[id.id] = index;
23 return index;
24}
25
27 const auto it = id_to_index_.find(id.id);
28 if (it == id_to_index_.end()) [[unlikely]] {
29 DagError error{
31 .message =
32 std::format("Node not found in DAG for system id '{}'", id.id),
33 .involved_nodes = {id},
34 };
35 return std::unexpected(std::move(error));
36 }
37 return it->second;
38}
39
41 if (from == to) [[unlikely]] {
42 DagError error{
44 .message =
45 std::format("Self-loop detected for system id '{}'", from.id),
46 .involved_nodes = {from},
47 };
48 return std::unexpected(std::move(error));
49 }
50
51 auto from_idx = IndexOf(from);
52 if (!from_idx) [[unlikely]] {
53 return std::unexpected(std::move(from_idx.error()));
54 }
55
56 auto to_idx = IndexOf(to);
57 if (!to_idx) [[unlikely]] {
58 return std::unexpected(std::move(to_idx.error()));
59 }
60
61 adjacency_[*from_idx].push_back(*to_idx);
62 return {};
63}
64
65auto Dag::Sort() const -> DagResult<std::vector<SystemId>> {
66 const size_t size = nodes_.size();
67 if (size == 0) [[unlikely]] {
68 return std::vector<SystemId>{};
69 }
70
71 std::vector<size_t> in_degree(size, 0);
72 for (size_t i = 0; i < size; ++i) {
73 for (size_t succ : adjacency_[i]) {
74 ++in_degree[succ];
75 }
76 }
77
78 using QueueEntry = std::pair<size_t, size_t>;
79 const auto cmp = [](const QueueEntry& lhs, const QueueEntry& rhs) {
80 return lhs.second > rhs.second;
81 };
82 std::priority_queue<QueueEntry, std::vector<QueueEntry>, decltype(cmp)> queue(
83 cmp);
84
85 for (size_t i = 0; i < size; ++i) {
86 if (in_degree[i] == 0) {
87 queue.emplace(i, nodes_[i].insertion_index);
88 }
89 }
90
91 std::vector<SystemId> result;
92 result.reserve(size);
93
94 while (!queue.empty()) {
95 const auto [current, _] = queue.top();
96 queue.pop();
97
98 result.push_back(nodes_[current].id);
99
100 for (const size_t succ : adjacency_[current]) {
101 if (--in_degree[succ] == 0) {
102 queue.emplace(succ, nodes_[succ].insertion_index);
103 }
104 }
105 }
106
107 if (result.size() != size) {
108 const auto cycle_path = ExtractCyclePath();
109 std::vector<SystemId> involved;
110 involved.reserve(cycle_path.size());
111 for (const size_t idx : cycle_path) {
112 involved.push_back(nodes_[idx].id);
113 }
114
115 DagError error{
117 .message =
118 std::format("Cycle detected in system ordering DAG with '{}' nodes",
119 cycle_path.size()),
120 .involved_nodes = std::move(involved),
121 };
122 return std::unexpected(std::move(error));
123 }
124
125 return result;
126}
127
128auto Dag::ExtractCyclePath() const -> std::vector<size_t> {
129 const size_t size = nodes_.size();
130
131 std::vector<size_t> in_degree(size, 0);
132 for (size_t i = 0; i < size; ++i) {
133 for (const size_t succ : adjacency_[i]) {
134 ++in_degree[succ];
135 }
136 }
137
138 size_t start = 0;
139 for (; start < size; ++start) {
140 if (in_degree[start] > 0) {
141 break;
142 }
143 }
144
145 if (start == size) {
146 return {};
147 }
148
149 std::vector<size_t> path;
150 std::vector<bool> visited(size, false);
151 std::vector<bool> on_stack(size, false);
152
153 struct Frame {
154 size_t node = 0;
155 size_t next_child = 0;
156 };
157
158 std::vector<Frame> stack;
159 stack.push_back({.node = start, .next_child = 0});
160 on_stack[start] = true;
161 path.push_back(start);
162
163 while (!stack.empty()) {
164 auto& frame = stack.back();
165
166 if (frame.next_child >= adjacency_[frame.node].size()) {
167 on_stack[frame.node] = false;
168 path.pop_back();
169 stack.pop_back();
170 continue;
171 }
172
173 size_t succ = adjacency_[frame.node][frame.next_child];
174 ++frame.next_child;
175
176 if (on_stack[succ]) {
177 const auto it = std::ranges::find(path, succ);
178 std::vector<size_t> cycle(it, path.end());
179 cycle.push_back(succ);
180 return cycle;
181 }
182
183 if (!visited[succ]) {
184 visited[succ] = true;
185 on_stack[succ] = true;
186 path.push_back(succ);
187 stack.push_back({.node = succ, .next_child = 0});
188 }
189 }
190
191 return {};
192}
193
194bool Dag::HasCycle() const {
195 const size_t size = nodes_.size();
196 if (size == 0) [[unlikely]] {
197 return false;
198 }
199
200 std::vector<size_t> in_degree(size, 0);
201 for (size_t i = 0; i < size; ++i) {
202 for (size_t succ : adjacency_[i]) {
203 ++in_degree[succ];
204 }
205 }
206
207 std::queue<size_t> queue;
208 for (size_t i = 0; i < size; ++i) {
209 if (in_degree[i] == 0) {
210 queue.push(i);
211 }
212 }
213
214 size_t processed = 0;
215 while (!queue.empty()) {
216 size_t current = queue.front();
217 queue.pop();
218 ++processed;
219
220 for (size_t succ : adjacency_[current]) {
221 if (--in_degree[succ] == 0) {
222 queue.push(succ);
223 }
224 }
225 }
226
227 return processed != size;
228}
229
230bool Dag::Reachable(SystemId from, SystemId to) const {
231 if (from == to) {
232 return true;
233 }
234
235 const auto from_idx = IndexOf(from);
236 const auto to_idx = IndexOf(to);
237 if (!from_idx || !to_idx) {
238 return false;
239 }
240
241 std::vector<bool> visited(nodes_.size(), false);
242 std::queue<size_t> queue;
243 queue.push(*from_idx);
244 visited[*from_idx] = true;
245
246 while (!queue.empty()) {
247 const size_t current = queue.front();
248 queue.pop();
249
250 for (const size_t succ : adjacency_[current]) {
251 if (succ == *to_idx) {
252 return true;
253 }
254 if (!visited[succ]) {
255 visited[succ] = true;
256 queue.push(succ);
257 }
258 }
259 }
260
261 return false;
262}
263
264} // namespace helios::ecs
size_t AddNode(SystemId id)
Adds a node for id.
Definition dag.cpp:16
auto Sort() const -> DagResult< std::vector< SystemId > >
Performs a topological sort and returns system ids in execution order.
Definition dag.cpp:65
auto AddEdge(SystemId from, SystemId to) -> DagResult< void >
Adds a directed edge from from to to (from must run before to).
Definition dag.cpp:40
bool HasCycle() const
Checks if this DAG has a cycle.
Definition dag.cpp:194
bool Reachable(SystemId from, SystemId to) const
Checks whether to is reachable from from along directed edges.
Definition dag.cpp:230
auto IndexOf(SystemId id) const -> DagResult< size_t >
Gets the internal index of id.
Definition dag.cpp:26
std::expected< T, DagError > DagResult
Definition dag.hpp:27
STL namespace.
Id for systems.
Definition system.hpp:146