MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
TreeEditor.hpp
1#pragma once
2
3#include "MorphologicalTree.hpp"
4
5#include <stdexcept>
6
7namespace mmcfilters {
8
22 friend class MorphologicalTree;
23
24private:
25 MorphologicalTree* tree_ = nullptr;
26 bool active_ = false;
27
35 class EditSessionPause {
36 private:
37 MorphologicalTree& tree_;
38 bool wasEditing_ = false;
39
40 public:
41 explicit EditSessionPause(MorphologicalTree& tree) noexcept
42 : tree_(tree), wasEditing_(tree.editSessionOpen_) {
43 tree_.editSessionOpen_ = false;
44 }
45
46 ~EditSessionPause() noexcept {
47 tree_.editSessionOpen_ = wasEditing_;
48 }
49 };
50
54 explicit TreeEditor(MorphologicalTree& tree) : tree_(&tree), active_(true) {
55 tree_->beginEditSession();
56 }
57
61 MorphologicalTree& tree() const {
62 if (!active_ || tree_ == nullptr) {
63 throw std::logic_error("TreeEditor operation requires an active edit session.");
64 }
65 return *tree_;
66 }
67
68public:
69 TreeEditor(const TreeEditor&) = delete;
70 TreeEditor& operator=(const TreeEditor&) = delete;
71
76 : tree_(other.tree_), active_(other.active_) {
77 other.tree_ = nullptr;
78 other.active_ = false;
79 }
80
81 TreeEditor& operator=(TreeEditor&&) = delete;
82
90 ~TreeEditor() = default;
91
98 [[nodiscard("Discarding a detached node id makes the staged edit impossible to complete safely")]] NodeId createDetachedNode() {
99 MorphologicalTree& t = tree();
100 return t.createDetachedNode();
101 }
102
107 MorphologicalTree& t = tree();
108 if (!t.isAlive(nodeId)) {
109 throw std::invalid_argument("TreeEditor::detach requires a live node.");
110 }
111 if (t.isRoot(nodeId)) {
112 throw std::invalid_argument("TreeEditor::detach cannot detach the connected root.");
113 }
114 t.detachNode(nodeId);
115 }
116
124 MorphologicalTree& t = tree();
125 if (!t.isAlive(nodeId) || !t.isAlive(newParentId)) {
126 throw std::invalid_argument("TreeEditor::reparent requires live node ids.");
127 }
128 if (t.isRoot(nodeId)) {
129 throw std::invalid_argument("TreeEditor::reparent cannot move the connected root.");
130 }
131 if (nodeId == newParentId) {
132 throw std::invalid_argument("TreeEditor::reparent requires distinct node ids.");
133 }
134 t.moveNode(nodeId, newParentId);
135 }
136
144 MorphologicalTree& t = tree();
145 if (!t.isAlive(parentId) || !t.isAlive(detachedNodeId)) {
146 throw std::invalid_argument("TreeEditor::attach requires live node ids.");
147 }
148 if (parentId == detachedNodeId) {
149 throw std::invalid_argument("TreeEditor::attach requires distinct node ids.");
150 }
151 if (t.getNodeParent(detachedNodeId) != detachedNodeId) {
152 throw std::invalid_argument("TreeEditor::attach expects a detached self-parented node.");
153 }
154 t.attachNode(parentId, detachedNodeId);
155 }
156
165 MorphologicalTree& t = tree();
166 if (!t.isAlive(parentId) || !t.isAlive(sourceId)) {
167 throw std::invalid_argument("TreeEditor::moveChildren requires live node ids.");
168 }
169 if (parentId == sourceId) {
170 throw std::invalid_argument("TreeEditor::moveChildren requires distinct node ids.");
171 }
172 t.moveChildren(parentId, sourceId);
173 }
174
182 MorphologicalTree& t = tree();
183 if (!t.isAlive(targetNodeId) || !t.isAlive(sourceNodeId)) {
184 throw std::invalid_argument("TreeEditor::moveProperPart requires live node ids.");
185 }
186 if (targetNodeId == sourceNodeId) {
187 throw std::invalid_argument("TreeEditor::moveProperPart requires distinct source and target nodes.");
188 }
189 if (!t.isProperPart(properPartId)) {
190 throw std::invalid_argument("TreeEditor::moveProperPart requires a valid proper-part id.");
191 }
192 t.moveProperPart(targetNodeId, sourceNodeId, properPartId);
193 }
194
202 MorphologicalTree& t = tree();
203 if (!t.isAlive(targetNodeId) || !t.isAlive(sourceNodeId)) {
204 throw std::invalid_argument("TreeEditor::moveProperParts requires live node ids.");
205 }
206 if (targetNodeId == sourceNodeId) {
207 throw std::invalid_argument("TreeEditor::moveProperParts requires distinct source and target nodes.");
208 }
209 t.moveProperParts(targetNodeId, sourceNodeId);
210 }
211
216 MorphologicalTree& t = tree();
217 if (!t.isAlive(parentNodeId) || !t.isAlive(childId)) {
218 throw std::invalid_argument("TreeEditor::removeChild requires live node ids.");
219 }
220 if (!t.hasChild(parentNodeId, childId)) {
221 throw std::invalid_argument("TreeEditor::removeChild requires a direct parent-child relation.");
222 }
223 t.removeChild(parentNodeId, childId, releaseNodeFlag);
224 }
225
230 MorphologicalTree& t = tree();
231 if (!t.isAlive(nodeId)) {
232 throw std::invalid_argument("TreeEditor::releaseNode requires a live node.");
233 }
234 if (t.isRoot(nodeId)) {
235 throw std::invalid_argument("TreeEditor::releaseNode cannot release the connected root.");
236 }
237 if (t.getNodeParent(nodeId) != nodeId) {
238 throw std::invalid_argument("TreeEditor::releaseNode expects a detached self-parented node.");
239 }
240 t.releaseNode(nodeId);
241 }
242
250 MorphologicalTree& t = tree();
251 if (!t.isAlive(nodeId)) {
252 throw std::invalid_argument("TreeEditor::setRoot requires a live node.");
253 }
254 t.setRoot(nodeId);
255 }
256
261 MorphologicalTree& t = tree();
262 EditSessionPause pause(t);
263 t.pruneNode(nodeId);
264 }
265
270 MorphologicalTree& t = tree();
271 EditSessionPause pause(t);
272 t.mergeNodeIntoParent(nodeId);
273 }
274
279 return active_ && tree_ != nullptr && tree_->hasDetachedAliveNodes();
280 }
281
286 if (!active_ || tree_ == nullptr) {
287 return {false, "TreeEditor validation requires an active edit session."};
288 }
289 return tree_->validateConnectedRootedTreeResult();
290 }
291
295 [[nodiscard("Inspect the validation result or use commit() for exception-based failure handling")]] TreeValidationResult validateAndCommit() noexcept {
297 if (!result.ok) {
298 return result;
299 }
300 tree_->endEditSession();
301 active_ = false;
302 return result;
303 }
304
311 void commit() {
313 if (!result.ok) {
314 throw std::runtime_error(result.message);
315 }
316 }
317
326 if (active_ && tree_ != nullptr) {
327#if defined(MMCFILTERS_ENABLE_ASSERTS)
329 assert(result.ok && "TreeEditor::commitUnchecked requires a valid tree when MMCFILTERS_ENABLE_ASSERTS is enabled.");
330#endif
331 tree_->endEditSession();
332 active_ = false;
333 }
334 }
335};
336
338 return TreeEditor(*this);
339}
340
341} // namespace mmcfilters
Mutable morphological tree built directly on proper parts and dense node ids.
TreeEditor edit()
Opens the only public entrypoint for staged structural mutations.
bool hasDetachedAliveNodes() const noexcept
Returns whether the tree currently contains alive detached nodes.
TreeValidationResult validateConnectedRootedTreeResult() const noexcept
Runs strong validation and returns the result instead of throwing.
Thin edit-session facade for multi-step topology updates.
TreeValidationResult validate() const noexcept
Runs strong validation without closing the session.
void detach(NodeId nodeId)
Detaches one non-root node from the connected rooted component.
bool hasDetachedAliveNodes() const noexcept
Returns whether the staged edit still has detached alive nodes.
void mergeNodeIntoParent(NodeId nodeId)
Applies the committed-safe parent merge inside the staged edit.
void reparent(NodeId nodeId, NodeId newParentId)
Reparents one live non-root node under another live node.
void attach(NodeId parentId, NodeId detachedNodeId)
Attaches one detached node back under the connected rooted tree.
NodeId createDetachedNode()
Creates a live detached node in the topological hierarchy.
void setRoot(NodeId nodeId)
Promotes nodeId to become the connected root.
void removeChild(NodeId parentNodeId, NodeId childId, bool releaseNodeFlag)
Detaches a direct child from its parent and optionally releases an empty detached slot.
void moveProperParts(NodeId targetNodeId, NodeId sourceNodeId)
Transfers every direct proper part from sourceNodeId to targetNodeId.
~TreeEditor()=default
Leaves an unfinished edit session open.
void releaseNode(NodeId nodeId)
Releases an empty detached non-root node slot.
TreeEditor(TreeEditor &&other) noexcept
Transfers the open edit-session handle without closing it.
void commit()
Finalizes the edit by validating that the tree is connected again.
void moveProperPart(NodeId targetNodeId, NodeId sourceNodeId, NodeId properPartId)
Transfers one direct proper part from sourceNodeId to targetNodeId.
void moveChildren(NodeId parentId, NodeId sourceId)
Transfers every direct child of sourceId under parentId.
void commitUnchecked() noexcept
Finalizes a staged edit without running the linear validation.
void pruneNode(NodeId nodeId)
Applies the committed-safe subtree prune inside the staged edit.
TreeValidationResult validateAndCommit() noexcept
Validates and closes the edit session on success.
Owning result for one computed scalar attribute layout and buffer.
Non-throwing validation result returned by edit-session checks.