entttree 0.1.0
Hierarchical entity management for EnTT
Loading...
Searching...
No Matches
traverse.h
Go to the documentation of this file.
1
14#pragma once
15
16#include <deque>
17#include <list>
18#include <optional>
19#include <type_traits>
20#include <utility>
21#include <vector>
22
23#include <entttree/defs.h>
24#include <entttree/generator.h>
25
26namespace entttree {
27
28/*****************************
29 * traversal class + concept *
30 *****************************/
31
43template <typename Node_, typename Successors_>
44struct Traversal {
45 using Node = Node_;
46 using Successors = Successors_;
47
49 std::optional<Node> root;
50
60
67 Traversal&& enable(bool enable) && {
68 if (not enable) root = std::nullopt;
69 return std::move(*this);
70 }
71
74 if (not enable) root = std::nullopt;
75 return *this;
76 }
77};
78
79
80/*****************************
81 * concepts + type helpers *
82 *****************************/
83
85template <typename T>
86using TraversalValue = std::remove_cvref_t<T>;
87
89template <typename T>
91
93template <typename T>
95
96
98template <typename G, typename T>
99concept GeneratorConcept = requires (G g) {
100 { g } -> std::convertible_to<bool>;
101 { *g } -> std::convertible_to<T>;
102 { ++g } -> std::convertible_to<G&>;
103};
104
105
107template <typename T, typename Node>
109requires (TraversalValue<T> t, Node& n) {
110 { t.root.has_value() } -> std::convertible_to<bool>;
111 { *(t.root) } -> std::convertible_to<Node>;
112 { t.successors(n) } -> GeneratorConcept<Node>;
113};
114
115
117template <typename T>
118concept AnyTraversalConcept = requires {
119 typename TraversalNode<T>;
120 typename TraversalSuccessors<T>;
122
123
125template <typename Successors, typename Node>
126using SuccessorGenerator = std::invoke_result_t<Successors&, Node&>;
127
129template <typename Successors, typename Node>
130using SuccessorOutput = decltype(*std::declval<SuccessorGenerator<Successors,Node>&>());
131
133template <typename Traversal>
137>;
138
140template <typename Traversal>
141using TraversalGenerator = std::remove_cvref_t<
145 >
146>;
147
148
149/*****************************
150 * traversal generation *
151 *****************************/
152
160template <typename Node, typename Successors>
161requires requires (Successors s, Node& n) {
162 { s(n) } -> GeneratorConcept<Node>;
163}
165 std::optional<Node>&& root,
166 Successors&& successors)
167{
168 using S = std::remove_cvref_t<Successors>;
169 return Traversal<Node,S> {
170 std::move(root),
171 std::forward<Successors>(successors)
172 };
173}
174
175
177template <typename Node, typename Successors>
178requires requires (Successors s, Node& n) {
179 { s(n) } -> GeneratorConcept<Node>;
180}
182 const std::optional<Node>& root,
183 Successors&& successors)
184{
185 using S = std::remove_cvref_t<Successors>;
186 return Traversal<Node,S> {
187 root,
188 std::forward<Successors>(successors)
189 };
190}
191
192
194template <typename Node, typename Successors>
195requires requires (Successors s, std::remove_cvref_t<Node>& n) {
196 { s(n) } -> GeneratorConcept<std::remove_cvref_t<Node>>;
197}
198auto make_traversal(
199 Node&& root,
200 Successors&& successors)
201{
202 using N = std::remove_cvref_t<Node>;
203 return make_traversal(
204 std::make_optional(N{std::forward<Node>(root)}),
205 std::forward<Successors>(successors)
206 );
207}
208
209
210/*****************************
211 * traversal transformations *
212 *****************************/
213
214namespace walk {
215
216
217/*****************************
218 * traversal adaptors *
219 *****************************/
220
232template <AnyTraversalConcept T, typename Filter>
235 using Node = typename Traversal::Node;
237
238 Traversal base = std::forward<T>(t);
239 return make_traversal(
240 std::move(base.root),
241 [
242 successors = std::move(base.successors),
243 should_explore = std::move(should_explore)
244 ] (Node& node) mutable -> MaybeGenerator<G>
245 {
246 if (should_explore(node)) {
247 return MaybeGenerator<G>{ successors(node) };
248 }
249 return MaybeGenerator<G>{};
250 }
251 );
252}
253
254
265template <AnyTraversalConcept T, typename Filter>
268 using Node = typename Traversal::Node;
269
270 Traversal base = std::forward<T>(t);
271 std::optional<Node> root;
272 if (base.root and should_admit(*base.root)) {
273 // Explicit copy/move boundary: traversal stores node handles by value.
274 root = std::move(*base.root);
275 }
276
277 return make_traversal(
278 std::move(root),
279 [
280 successors = std::move(base.successors),
281 should_admit = std::move(should_admit)
282 ] (Node& node) mutable -> Generator<Node>
283 {
284 for (auto g = successors(node); g; ++g) {
285 // Explicit copy/move boundary: successor output is materialized as Node.
286 Node child = *g;
287 if (should_admit(child)) {
288 co_yield std::move(child);
289 }
290 }
291 }
292 );
293}
294
295
304template <AnyTraversalConcept T>
307 using Node = typename Traversal::Node;
308
309 Traversal base = std::forward<T>(t);
310 return make_traversal(
311 std::move(base.root),
312 [
313 successors = std::move(base.successors)
314 ] (Node& node) mutable -> Generator<Node>
315 {
316 // Generic reverse requires buffering children first.
317 std::vector<Node> children;
318 for (auto g = successors(node); g; ++g) {
319 children.emplace_back(*g);
320 }
321 for (auto it = children.rbegin(); it != children.rend(); ++it) {
322 co_yield std::move(*it);
323 }
324 }
325 );
326}
327
328
339template <AnyTraversalConcept Traversal, typename Transform>
342 using Node = typename T::Node;
343 // map_nodes owns transformed nodes by value, so reference returns are decayed.
344 using Value = std::remove_cvref_t<std::invoke_result_t<Transform&, Node&>>;
345
346 T base = std::forward<Traversal>(t);
347 std::optional<Value> root_value;
348 if (base.root) {
349 root_value = std::make_optional(xform(*base.root));
350 }
351
352 return make_traversal(
353 std::move(root_value),
354 [
355 successors = std::move(base.successors),
356 xform = std::forward<Transform>(xform)
357 ]
358 (Value& node) mutable -> Generator<Value>
359 {
360 for (auto g = successors(node); g; ++g) {
361 auto&& child = *g;
362 co_yield xform(child);
363 }
364 }
365 );
366}
367
368
369/*****************************
370 * traversal walkers *
371 *****************************/
372
374namespace detail {
375
376template <typename Node, typename SuccessorGenerator>
377struct DfsEntry {
378 Node node;
379 SuccessorGenerator successors;
380
381 template <typename N, typename Successors>
382 DfsEntry(N&& n, Successors& next):
383 node(std::forward<N>(n)),
384 successors(next(node)) {}
385};
386
387
388template <DfsOrder recursion_order, typename Node, typename Successors>
389Generator<Node> dfs(
390 std::optional<Node> root,
391 Successors successors)
392{
393 using SuccessorGenerator = std::remove_cvref_t<
394 std::invoke_result_t<Successors&, Node&>
395 >;
396 using StackEntry = DfsEntry<Node, SuccessorGenerator>;
397
398 if (not root) co_return;
399
400 std::list<StackEntry> stack;
401 stack.emplace_back(std::move(*root), successors);
402
403 if constexpr (recursion_order == DfsOrder::ShallowFirst) {
404 co_yield stack.back().node;
405 }
406
407 while (not stack.empty()) {
408 StackEntry& entry = stack.back();
409 if (entry.successors) {
410 // Explicit copy/move boundary: child is stored in traversal frame.
411 Node next_node = *entry.successors;
412 ++entry.successors;
413
414 stack.emplace_back(
415 std::move(next_node),
416 successors
417 );
418 if constexpr (recursion_order == DfsOrder::ShallowFirst) {
419 co_yield stack.back().node;
420 }
421 } else {
422 if constexpr (recursion_order == DfsOrder::DeepFirst) {
423 co_yield entry.node;
424 }
425 stack.pop_back();
426 }
427 }
428}
429
430
431template <typename Node, typename Successors>
432Generator<Node> bfs(
433 std::optional<Node> root,
434 Successors successors)
435{
436 if (not root) co_return;
437
438 std::deque<Node> queue;
439 queue.emplace_back(std::move(*root));
440
441 while (not queue.empty()) {
442 Node node = std::move(queue.front());
443 queue.pop_front();
444
445 for (auto g = successors(node); g; ++g) {
446 // Explicit copy/move boundary: queue stores node handles by value.
447 Node child = *g;
448 queue.emplace_back(std::move(child));
449 }
450
451 co_yield std::move(node);
452 }
453}
454
455} // namespace detail
457
458
468template <
469 DfsOrder recursion_order = DfsOrder::ShallowFirst,
470 AnyTraversalConcept T
471>
472auto dfs(T&& t) {
474 using Node = typename Traversal::Node;
475
476 Traversal base = std::forward<T>(t);
477 return detail::dfs<recursion_order, Node>(
478 std::move(base.root),
479 std::move(base.successors)
480 );
481}
482
483
485template <AnyTraversalConcept T>
486auto dfs(
487 T&& t,
489{
490 if (recursion_order == DfsOrder::ShallowFirst) {
491 return dfs<DfsOrder::ShallowFirst>(std::forward<T>(t));
492 }
493 return dfs<DfsOrder::DeepFirst>(std::forward<T>(t));
494}
495
496
502template <AnyTraversalConcept T>
503auto bfs(T&& t) {
505 using Node = typename Traversal::Node;
506
507 Traversal base = std::forward<T>(t);
508 return detail::bfs<Node>(
509 std::move(base.root),
510 std::move(base.successors)
511 );
512}
513
514} // namespace walk
515
516
517/*****************************
518 * utility transformation *
519 *****************************/
520
534template <
535 typename Node,
536 typename Value,
537 typename Trav,
538 typename Compose,
539 typename Get
540>
541requires TraversalConcept<Trav, Node>
542 and requires (Compose c, std::optional<Value>& parent, Node n) {
543 { c(parent, std::move(n)) } -> std::convertible_to<std::optional<Value>>;
544 }
545 and std::invocable<Get&, Value&>
546auto inductive_transform(Trav&& t, Compose&& make_successor, Get&& get_node) {
547 using Traversal = TraversalValue<Trav>;
548
549 Traversal base = std::forward<Trav>(t);
550
551 std::optional<Value> root_value;
552 if (base.root) {
553 std::optional<Value> no_parent = std::nullopt;
554 Node root_node = *base.root;
555 root_value = make_successor(no_parent, std::move(root_node));
556 }
557
558 return make_traversal(
559 std::move(root_value),
560 [
561 successors = std::move(base.successors),
562 get_node = std::forward<Get>(get_node),
563 make_successor = std::forward<Compose>(make_successor)
564 ] (Value& parent) mutable -> Generator<Value> {
565 auto&& parent_node = get_node(parent);
566 std::optional<Value> parent_value = parent;
567 for (auto g = successors(parent_node); g; ++g) {
568 Node child = *g;
569 std::optional<Value> value = make_successor(
570 parent_value,
571 std::move(child)
572 );
573 if (value) co_yield std::move(*value);
574 }
575 }
576 );
577}
578
579
580} // namespace entttree
Satisfied by any type that is a Traversal over its own Node type.
Definition traverse.h:118
A type G that can be advanced, dereferenced, and tested for exhaustion.
Definition traverse.h:99
Satisfied by types that have a root and a successors function yielding Node.
Definition traverse.h:108
Common type aliases, enumerations, and utility functions used throughout entttree.
DfsOrder
Controls the visit order for depth-first traversal.
Definition defs.h:35
Coroutine-based generator and related wrapper types.
A system for maintaining parent-child relationships on entities.
Definition hierarchy.h:37
A lazy description of a rooted graph traversal.
Definition traverse.h:44
std::optional< Node > root
The starting point of the traversal, or empty to disable it.
Definition traverse.h:49
Traversal & enable(bool enable) &
Conditionally disable the traversal.
Definition traverse.h:73
Successors successors
A function returning a generator of the successors of a node.
Definition traverse.h:59
Traversal && enable(bool enable) &&
Conditionally disable the traversal.
Definition traverse.h:67
auto reverse_successors(T &&t)
Reverse the order of each node's successors.
Definition traverse.h:305
auto bfs(T &&t)
Flatten a Traversal into a breadth-first Generator.
Definition traverse.h:503
auto dfs(T &&t)
Flatten a Traversal into a depth-first Generator.
Definition traverse.h:472
decltype(*std::declval< SuccessorGenerator< Successors, Node > & >()) SuccessorOutput
The value type yielded by a successor generator.
Definition traverse.h:130
auto prune_if(T &&t, Filter should_explore)
Prevent certain nodes from generating successors.
Definition traverse.h:233
std::remove_cvref_t< T > TraversalValue
Strip references/cv to get the bare Traversal type.
Definition traverse.h:86
typename TraversalValue< T >::Node TraversalNode
Extract the Node type from a Traversal (after stripping cvref).
Definition traverse.h:90
std::remove_cvref_t< SuccessorGenerator< TraversalSuccessors< Traversal >, TraversalNode< Traversal > > > TraversalGenerator
The concrete generator type produced by a traversal's successor function.
Definition traverse.h:146
and
Transform a traversal inductively, threading parent context through successors.
Definition traverse.h:542
typename TraversalValue< T >::Successors TraversalSuccessors
Extract the Successors callable type from a Traversal.
Definition traverse.h:94
auto make_traversal(std::optional< Node > &&root, Successors &&successors)
Construct a Traversal from an optional root and a successor function.
Definition traverse.h:164
std::invoke_result_t< Successors &, Node & > SuccessorGenerator
The generator type returned by a successor function.
Definition traverse.h:126
auto map_nodes(Traversal &&t, Transform &&xform)
Transform the node type of a traversal.
Definition traverse.h:340
auto exclude_if(T &&t, Filter should_admit)
Remove certain nodes from the graph entirely.
Definition traverse.h:266