entttree 0.1.0
Hierarchical entity management for EnTT
Loading...
Searching...
No Matches
hierarchy.h
Go to the documentation of this file.
1
6#pragma once
7
8#include <algorithm>
9#include <cassert>
10
12#include <entttree/traverse.h>
13
14namespace entttree {
15
16
36template <typename HTag>
38
40
41 /****************************
42 * Typed signals
43 ****************************/
44
46 entt::sigh<void(entt::entity, PC)> on_added;
48 entt::sigh<void(entt::entity, PC)> on_removed;
50 entt::sigh<void(entt::entity, PC, PC)> on_changed;
51
52 /****************************
53 * Construction
54 ****************************/
55
56 explicit HierarchySystem(entt::registry& reg): _reg(reg) {}
57
58 // non-copyable, non-movable (signal connections hold `this`)
59 HierarchySystem(const HierarchySystem&) = delete;
60 HierarchySystem& operator=(const HierarchySystem&) = delete;
61
62 /****************************
63 * Mutation API
64 ****************************/
65
75 entt::entity child,
76 entt::entity parent,
77 std::optional<Position> position = std::nullopt)
78 {
79 bool maybe_dupe = true;
80
81 // ensure the parent has a child list
82 auto it = _children.find(parent);
83 if (it == _children.end()) {
84 position = position.value_or(Position{});
85 maybe_dupe = false;
86 std::tie(it, std::ignore) = _children.emplace(
87 parent,
88 ChildList{}
89 );
90 }
91 ChildList& children = it->second;
92 if (not position and children.size() > 0) {
93 position = children.back().position.after();
94 maybe_dupe = false;
95 }
96
97 // read old state before mutation
98 auto* old_pc = _reg.try_get<PC>(child);
99 std::optional<PC> old_val;
100 if (old_pc) old_val = *old_pc;
101
102 // write the component
103 _reg.emplace_or_replace<PC>(child, parent, *position);
104
105 // update children cache
106 if (old_val and old_val->parent == parent) {
107 // same parent, reorder
108 auto src = std::ranges::lower_bound(
109 children,
110 ChildEntry {.eid = child, .position = old_val->position},
111 [](const ChildEntry& a, const ChildEntry& b) { return a < b; }
112 );
113 auto dst = std::ranges::lower_bound(
114 children, *position, std::ranges::less{}, &ChildEntry::position
115 );
116 src->position = *position;
117 if (dst != src) {
118 bool duped = maybe_dupe
119 and _dedupe_position(children, src->position, dst);
120 if (dst < src) {
121 std::rotate(dst, src, src + 1);
122 src = dst;
123 } else {
124 std::rotate(src, src + 1, dst);
125 src = dst - 1;
126 }
127 if (duped) {
128 _reg.get<PC>(child).position = src->position;
129 }
130 }
131 } else {
132 if (old_val) {
133 // remove from old parent's child list.
134 // this may invalidate `children` ref if the old parent's
135 // entry is erased from the DenseMap, so we re-lookup after.
136 _remove_from_child_list(old_val->parent, child, old_val->position);
137 it = _children.find(parent);
138 }
139 // insert into new parent's child list
140 ChildList& new_children = it->second;
141 auto dst_pos = std::ranges::lower_bound(
142 new_children, *position, std::ranges::less{}, &ChildEntry::position
143 );
144 if (maybe_dupe) _dedupe_position(new_children, *position, dst_pos);
145 new_children.insert(
146 dst_pos,
147 ChildEntry {.eid = child, .position = *position}
148 );
149 }
150
151 // emit signal
152 PC new_val {parent, *position};
153 if (old_val) {
154 if (*old_val != new_val) {
155 on_changed.publish(child, *old_val, new_val);
156 }
157 } else {
158 on_added.publish(child, new_val);
159 }
160
161 return *position;
162 }
163
164
170 std::optional<PC> unparent(entt::entity child) {
171 auto* old_pc = _reg.try_get<PC>(child);
172 if (not old_pc) return std::nullopt;
173
174 PC old_val = *old_pc;
175 _reg.erase<PC>(child);
176
177 _remove_from_child_list(old_val.parent, child, old_val.position);
178
179 on_removed.publish(child, old_val);
180 return old_val;
181 }
182
183
189 std::optional<Position> set_child_position(entt::entity child, Position position) {
190 auto* pc = _reg.try_get<PC>(child);
191 if (not pc) return std::nullopt;
192 if (pc->position == position) return std::nullopt;
193
194 PC old_val = *pc;
195 auto it = _children.find(pc->parent);
196 assert(it != _children.end());
197 ChildList& children = it->second;
198
199 auto old_loc = std::ranges::lower_bound(
200 children, ChildEntry {.eid = child, .position = old_val.position},
201 [](const ChildEntry& a, const ChildEntry& b) { return a < b; }
202 );
203 auto new_loc = std::ranges::lower_bound(
204 children, position, std::ranges::less{}, &ChildEntry::position
205 );
206 if (new_loc != old_loc) {
207 if (new_loc < old_loc) {
208 std::rotate(new_loc, old_loc, old_loc + 1);
210 } else {
211 std::rotate(old_loc, old_loc + 1, new_loc);
212 old_loc = new_loc - 1;
213 }
214 }
215 old_loc->position = position;
216
217 pc->position = position;
218 PC new_val = *pc;
219 on_changed.publish(child, old_val, new_val);
220 return position;
221 }
222
223
232 std::optional<Position> order_child_before(entt::entity child, entt::entity before) {
233 if (child == before) return std::nullopt;
234
235 auto* pc = _reg.try_get<PC>(child);
236 if (not pc) return std::nullopt;
237
238 auto it = _children.find(pc->parent);
239 assert(it != _children.end());
240 ChildList& children = it->second;
241
242 auto child_pos = std::ranges::lower_bound(
243 children, ChildEntry {.eid = child, .position = pc->position},
244 [](const ChildEntry& a, const ChildEntry& b) { return a < b; }
245 );
246 assert(child_pos != children.end());
247
249 auto* sibling_pc = _reg.try_get<PC>(before);
250 if (sibling_pc and sibling_pc->parent == pc->parent) {
251 auto before_pos = std::ranges::lower_bound(
252 children, ChildEntry {.eid = before, .position = sibling_pc->position},
253 [](const ChildEntry& a, const ChildEntry& b) { return a < b; }
254 );
255
256 if (child_pos == before_pos - 1) {
257 return child_pos->position;
258 } else if (before_pos == children.begin()) {
259 new_pos = before_pos->position.before();
260 } else {
261 new_pos = before_pos[-1].position.between(before_pos->position);
262 }
263
264 if (child_pos < before_pos) {
265 std::rotate(child_pos, child_pos + 1, before_pos);
266 child_pos = before_pos - 1;
267 } else {
268 std::rotate(before_pos, child_pos, child_pos + 1);
270 }
271 } else {
272 // sibling not found or different parent; put at end
273 if (child_pos == children.end() - 1) {
274 return child_pos->position;
275 }
276 new_pos = children.back().position.after();
277 std::rotate(child_pos, child_pos + 1, children.end());
278 child_pos = children.end() - 1;
279 }
280
281 PC old_val = *pc;
282 child_pos->position = new_pos;
283 pc->position = new_pos;
284
285 on_changed.publish(child, old_val, *pc);
286 return new_pos;
287 }
288
289
290 /****************************
291 * Queries
292 ****************************/
293
295 size_t size() const {
296 return _reg.template view<const PC>().size();
297 }
298
300 entt::entity parent_of(entt::entity node) const {
301 auto* pc = _reg.try_get<PC>(node);
302 return pc ? pc->parent : entt::null;
303 }
304
306 std::optional<Position> position_of(entt::entity node) const {
307 auto* pc = _reg.try_get<PC>(node);
308 if (pc) return pc->position;
309 return std::nullopt;
310 }
311
313 std::optional<PC> get_connection(entt::entity node) const {
314 auto* pc = _reg.try_get<PC>(node);
315 if (pc) return *pc;
316 return std::nullopt;
317 }
318
324 size_t child_count(entt::entity parent) const {
325 auto it = _children.find(parent);
326 if (it == _children.end()) return 0;
327 return it->second.size();
328 }
329
331 std::optional<Position> last_child_position(entt::entity parent) const {
332 auto it = _children.find(parent);
333 if (it == _children.end()) return std::nullopt;
334 const ChildList& children = it->second;
335 if (children.empty()) return std::nullopt;
336 return children.back().position;
337 }
338
339
340 /****************************
341 * Traversal generators
342 ****************************/
343
352 entt::entity parent,
353 SiblingOrder order) const
354 {
355 auto it = _children.find(parent);
356 if (it == _children.end()) co_return;
357 const ChildList& ch = it->second;
358 if (order == SiblingOrder::Forward) {
359 for (const auto& c : ch) {
360 co_yield NodeEntry {c.eid, parent, c.position};
361 }
362 } else {
363 for (const auto& c : std::ranges::reverse_view(ch)) {
364 co_yield NodeEntry {c.eid, parent, c.position};
365 }
366 }
367 }
368
369
379 Generator<NodeEntry> ancestors(entt::entity node) const {
380 entt::entity cur = node;
381 while (cur != entt::null) {
382 auto* pc = _reg.try_get<PC>(cur);
383 if (not pc) co_return;
384 co_yield NodeEntry {cur, pc->parent, pc->position};
385 cur = pc->parent;
386 }
387 }
388
389
396 auto traverse(entt::entity root, SiblingOrder order) const {
398 root,
399 parent_of(root),
400 position_of(root).value_or(Position{})
401 };
402 return make_traversal(
403 std::make_optional(root_entry),
404 [this, order] (NodeEntry& node) {
405 return this->children(node.node_id, order);
406 }
407 );
408 }
409
412 entt::entity root,
413 SiblingOrder sibling_order = SiblingOrder::Forward,
414 DfsOrder recursion_order = DfsOrder::ShallowFirst) const
415 {
416 return entttree::walk::dfs(
417 this->traverse(root, sibling_order),
419 );
420 }
421
428 TreePath path(entt::entity node) const {
429 TreePath p;
430 p.push_back(node);
431 auto* pc = _reg.try_get<PC>(node);
432 while (pc and pc->parent != entt::null) {
433 p.push_back(pc->parent);
434 pc = _reg.try_get<PC>(pc->parent);
435 }
436 std::reverse(p.begin(), p.end());
437 return p;
438 }
439
440
447 entt::entity node_a,
448 entt::entity node_b) const
449 {
452 size_t n = std::min(p0.size(), p1.size());
453 entt::entity common = entt::null;
454 for (size_t i = 0; i < n; ++i) {
455 if (p0[i] == p1[i]) {
456 common = p0[i];
457 } else {
458 break;
459 }
460 }
461 return common;
462 }
463
464
466 bool is_ancestor_of(entt::entity ancestor, entt::entity descendant) const {
467 auto* pc = _reg.try_get<PC>(descendant);
468 while (pc and pc->parent != entt::null) {
469 if (pc->parent == ancestor) return true;
470 pc = _reg.try_get<PC>(pc->parent);
471 }
472 return false;
473 }
474
475
476private:
477
478 entt::registry& _reg;
480
481 #ifndef NDEBUG
482 bool _publishing = false;
483 #endif
484
485
486 void _remove_from_child_list(
487 entt::entity parent,
488 entt::entity child,
489 const Position& pos)
490 {
491 auto it = _children.find(parent);
492 if (it == _children.end()) return;
493 ChildList& ch = it->second;
494 auto loc = std::ranges::lower_bound(
495 ch,
496 ChildEntry {.eid = child, .position = pos},
497 [](const ChildEntry& a, const ChildEntry& b) { return a < b; }
498 );
499 if (loc != ch.end()) ch.erase(loc);
500 if (ch.empty()) _children.erase(it);
501 }
502
503
504 bool _dedupe_position(
505 const ChildList& v,
506 Position& p,
507 typename ChildList::iterator next)
508 {
509 bool at_start = next == v.begin();
510 bool at_end = next == v.end();
511 auto prev = not at_start ? std::prev(next) : next;
512 bool duped = not at_start and p == prev->position;
513 duped |= not at_end and p == next->position;
514 if (duped) [[unlikely]] {
515 if (not (at_start or at_end)) {
516 p = prev->position.between(next->position);
517 } else if (not at_start) {
518 p = prev->position.after();
519 } else {
520 p = next->position.before();
521 }
522 }
523 return duped;
524 }
525
526};
527
528
529} // namespace entttree
DfsOrder
Controls the visit order for depth-first traversal.
Definition defs.h:35
SiblingOrder
Controls the iteration order of siblings within a parent.
Definition defs.h:43
ECS components and node types used by the hierarchy, transform, and bounds systems.
std::vector< ChildEntry > ChildList
Sorted list of children for a single parent.
Internal representation of a child in the sorted children cache.
Position position
Sibling ordering key.
entt::entity eid
Child entity.
A system for maintaining parent-child relationships on entities.
Definition hierarchy.h:37
entt::entity parent_of(entt::entity node) const
Returns the parent of node, or entt::null if the node is a root or not in the hierarchy.
Definition hierarchy.h:300
std::optional< Position > last_child_position(entt::entity parent) const
Returns the position of the last child, or std::nullopt if there are no children.
Definition hierarchy.h:331
Generator< NodeEntry > traverse_dfs(entt::entity root, SiblingOrder sibling_order=SiblingOrder::Forward, DfsOrder recursion_order=DfsOrder::ShallowFirst) const
Convenience: flatten a depth-first traversal into a generator.
Definition hierarchy.h:411
std::optional< PC > get_connection(entt::entity node) const
Returns the full ParentConnection for node, or std::nullopt if not in the hierarchy.
Definition hierarchy.h:313
Generator< NodeEntry > ancestors(entt::entity node) const
Returns a generator which yields the ancestors of a given node.
Definition hierarchy.h:379
bool is_ancestor_of(entt::entity ancestor, entt::entity descendant) const
Returns true if ancestor is a strict ancestor of descendant.
Definition hierarchy.h:466
auto traverse(entt::entity root, SiblingOrder order) const
Returns a Traversal of the hierarchy rooted at root.
Definition hierarchy.h:396
std::optional< Position > order_child_before(entt::entity child, entt::entity before)
Move a child to the position before a sibling.
Definition hierarchy.h:232
entt::sigh< void(entt::entity, PC)> on_added
Emitted after a child is added to the hierarchy. Args: (child, new_connection).
Definition hierarchy.h:46
Generator< NodeEntry > children(entt::entity parent, SiblingOrder order) const
Visit each child of a given node.
Definition hierarchy.h:351
entt::sigh< void(entt::entity, PC)> on_removed
Emitted after a child is removed from the hierarchy. Args: (child, old_connection).
Definition hierarchy.h:48
Position set_parent(entt::entity child, entt::entity parent, std::optional< Position > position=std::nullopt)
Set the parent of a child entity.
Definition hierarchy.h:74
size_t child_count(entt::entity parent) const
Returns the number of children of a given node.
Definition hierarchy.h:324
TreePath path(entt::entity node) const
Returns the path from the root to the given node.
Definition hierarchy.h:428
entt::sigh< void(entt::entity, PC, PC)> on_changed
Emitted after a child's parent or position changes. Args: (child, old_connection, new_connection).
Definition hierarchy.h:50
std::optional< Position > set_child_position(entt::entity child, Position position)
Change the position of a child among its siblings.
Definition hierarchy.h:189
entt::entity deepest_common_ancestor(entt::entity node_a, entt::entity node_b) const
Returns the deepest common ancestor of two nodes.
Definition hierarchy.h:446
size_t size() const
The number of non-root nodes in the hierarchy (i.e. entities with a parent).
Definition hierarchy.h:295
std::optional< Position > position_of(entt::entity node) const
Returns the sibling position of node, or std::nullopt if not in the hierarchy.
Definition hierarchy.h:306
std::optional< PC > unparent(entt::entity child)
Remove a child from its parent.
Definition hierarchy.h:170
A node handle yielded by hierarchy traversals and queries.
entt::entity node_id
The entity this entry represents.
A fractional index with strong ordering, used for sibling positioning.
Definition position.h:27
A path from a root entity down to a descendant.
Lazy, composable graph traversal framework built on C++20 generators.
and
Transform a traversal inductively, threading parent context through successors.
Definition traverse.h:542
auto make_traversal(std::optional< Node > &&root, Successors &&successors)
Construct a Traversal from an optional root and a successor function.
Definition traverse.h:164