45 std::size_t mutationVersion_ = 0;
47 TreeEditValidationMode validationMode_ = TreeEditValidationMode::Complete;
56 : editor_(&
editor), tree_(
editor.tree_), mutationVersion_(
editor.tree_ !=
nullptr ?
editor.tree_->getMutationVersion() : 0),
75 : editor_(
other.editor_), tree_(
other.tree_), mutationVersion_(
other.mutationVersion_), validationMode_(
other.validationMode_) {
76 other.editor_ =
nullptr;
77 other.tree_ =
nullptr;
101 template <
class Id, Id Inval
idId>
class DeltaIdSet {
103 std::vector<Id> ids_;
105 std::vector<Id> table_;
113 [[
nodiscard]]
static std::size_t hash(
Id id)
noexcept {
114 return static_cast<std::size_t
>(
static_cast<std::uint32_t
>(id) * std::uint32_t{2654435761u});
122 void rebuild(std::size_t capacity) {
123 std::vector<Id> newTable(capacity, InvalidId);
124 const std::size_t mask = capacity - 1;
126 std::size_t slot = hash(
id) & mask;
127 while (newTable[slot] != InvalidId) {
128 slot = (slot + 1) & mask;
132 table_ = std::move(newTable);
142 [[nodiscard]]
bool contains(Id
id)
const noexcept {
143 if (
id == InvalidId || table_.empty()) {
146 const std::size_t mask = table_.size() - 1;
147 std::size_t slot = hash(
id) & mask;
148 while (table_[slot] != InvalidId) {
149 if (table_[slot] ==
id) {
152 slot = (slot + 1) & mask;
163 [[nodiscard]]
bool insert(Id
id) {
164 if (
id == InvalidId) {
167 if (table_.empty()) {
170 }
else if ((ids_.size() + 1) * 10 > table_.size() * 7) {
171 rebuild(table_.size() * 2);
174 const std::size_t mask = table_.size() - 1;
175 std::size_t slot = hash(
id) & mask;
176 while (table_[slot] != InvalidId) {
177 if (table_[slot] ==
id) {
180 slot = (slot + 1) & mask;
187 [[nodiscard]]
const std::vector<Id>&
193 entries() const noexcept {
199 using DeltaNodeSet = DeltaIdSet<NodeId, InvalidNode>;
202 using DeltaPixelSet = DeltaIdSet<PixelId, InvalidPixel>;
205 struct NodeRollbackState {
221 std::uint8_t alive = 0;
227 int properPartCardinality = 0;
231 struct PixelRollbackState {
243 enum class FreeListMutation { Popped, Pushed };
246 struct FreeListRollbackState {
248 FreeListMutation mutation = FreeListMutation::Popped;
260 struct RollbackJournal {
262 std::size_t originalNodeSlots = 0;
268 std::optional<NodeId> preservedExternalNodeIdOffset;
270 std::size_t nodeStructureVersion = 0;
272 std::size_t topologyVersion = 0;
274 std::size_t properPartVersion = 0;
276 std::size_t mutationVersion = 0;
278 DeltaNodeSet capturedNodes;
280 DeltaPixelSet capturedPixels;
282 std::vector<NodeRollbackState> nodes;
284 std::vector<PixelRollbackState> pixels;
286 std::vector<FreeListRollbackState> freeListMutations;
290 MorphologicalTree* tree_ =
nullptr;
292 bool active_ =
false;
294 bool recoverable_ =
false;
296 std::unique_ptr<RollbackJournal> rollbackJournal_;
298 DeltaNodeSet touchedNodes_;
300 int detachedNodeBalance_ = 0;
302 int unsupportedLeafBalance_ = 0;
304 bool incrementalValidationSupported_ =
true;
306 bool invariantsEstablishedByConstruction_ =
false;
315 class EditSessionPause {
318 MorphologicalTree& tree_;
320 bool wasEditing_ =
false;
328 explicit EditSessionPause(MorphologicalTree& tree) noexcept : tree_(tree), wasEditing_(tree.editSessionOpen_) { tree_.editSessionOpen_ =
false; }
333 ~EditSessionPause() noexcept { tree_.editSessionOpen_ = wasEditing_; }
342 explicit TreeEditor(MorphologicalTree& tree,
bool invariantsEstablishedByConstruction =
false)
343 : tree_(&tree), active_(true), recoverable_(!invariantsEstablishedByConstruction),
344 invariantsEstablishedByConstruction_(invariantsEstablishedByConstruction) {
345 tree_->beginEditSession();
351 void ensureRollbackJournal() {
352 if (!recoverable_ || rollbackJournal_) {
355 auto journal = std::make_unique<RollbackJournal>();
356 journal->originalNodeSlots = tree_->nodeParent_.size();
357 journal->root = tree_->rootNodeId_;
358 journal->numNodes = tree_->numNodes_;
359 journal->preservedExternalNodeIdOffset = tree_->preservedExternalNodeIdOffset_;
360 journal->nodeStructureVersion = tree_->nodeStructureVersion_;
361 journal->topologyVersion = tree_->topologyVersion_;
362 journal->properPartVersion = tree_->properPartVersion_;
363 journal->mutationVersion = tree_->mutationVersion_;
364 rollbackJournal_ = std::move(journal);
372 MorphologicalTree& tree()
const {
373 if (!active_ || tree_ ==
nullptr) {
374 throw std::logic_error(
"TreeEditor operation requires an active edit session.");
389 void finishCommit(TreeEditValidationMode validationMode)
noexcept {
390 if (active_ && tree_ !=
nullptr) {
391 tree_->recordEditCommit(validationMode);
392 tree_->endEditSession();
394 recoverable_ =
false;
395 rollbackJournal_.reset();
404 void captureNodeForRollback(NodeId node) {
405 if (!recoverable_ || node < 0 ||
static_cast<std::size_t
>(node) >= tree_->nodeParent_.size()) {
408 ensureRollbackJournal();
409 if (
static_cast<std::size_t
>(node) >= rollbackJournal_->originalNodeSlots || rollbackJournal_->capturedNodes.contains(node)) {
412 rollbackJournal_->nodes.reserve(rollbackJournal_->nodes.size() + 1);
413 if (!rollbackJournal_->capturedNodes.insert(node)) {
416 const std::size_t slot =
static_cast<std::size_t
>(node);
417 rollbackJournal_->nodes.push_back({node, tree_->nodeParent_[slot], tree_->firstChild_[slot], tree_->nextSibling_[slot], tree_->prevSibling_[slot],
418 tree_->lastChild_[slot], tree_->numChildrenByNode_[slot], tree_->alive_[slot], tree_->properHead_[slot],
419 tree_->properTail_[slot], tree_->properPartCardinalityByNode_[slot]});
427 void capturePixelForRollback(PixelId pixel) {
428 if (!recoverable_ || !tree_->isPixel(pixel)) {
431 ensureRollbackJournal();
432 if (rollbackJournal_->capturedPixels.contains(pixel)) {
435 rollbackJournal_->pixels.reserve(rollbackJournal_->pixels.size() + 1);
436 if (!rollbackJournal_->capturedPixels.insert(pixel)) {
439 const std::size_t slot =
static_cast<std::size_t
>(pixel);
440 rollbackJournal_->pixels.push_back({pixel, tree_->smallestNodeMap_[slot], tree_->nextProperPart_[slot], tree_->prevProperPart_[slot]});
448 void captureNodeLinkNeighborhood(NodeId node) {
449 if (node < 0 ||
static_cast<std::size_t
>(node) >= tree_->nodeParent_.size()) {
452 captureNodeForRollback(node);
453 captureNodeForRollback(tree_->nodeParent_[
static_cast<std::size_t
>(node)]);
454 captureNodeForRollback(tree_->prevSibling_[
static_cast<std::size_t
>(node)]);
455 captureNodeForRollback(tree_->nextSibling_[
static_cast<std::size_t
>(node)]);
463 void capturePixelLinkNeighborhood(PixelId pixel) {
464 if (!tree_->isPixel(pixel)) {
467 capturePixelForRollback(pixel);
468 capturePixelForRollback(tree_->prevProperPart_[
static_cast<std::size_t
>(pixel)]);
469 capturePixelForRollback(tree_->nextProperPart_[
static_cast<std::size_t
>(pixel)]);
477 void prepareFreeListGrowth(std::size_t count) {
478 if (!recoverable_ || count == 0) {
481 ensureRollbackJournal();
482 rollbackJournal_->freeListMutations.reserve(rollbackJournal_->freeListMutations.size() + count);
483 tree_->freeNodeIds_.reserve(tree_->freeNodeIds_.size() + count);
491 void recordFreeListGrowth(std::size_t previousSize)
noexcept {
492 if (!rollbackJournal_) {
495 for (std::size_t i = previousSize; i < tree_->freeNodeIds_.size(); ++i) {
496 rollbackJournal_->freeListMutations.push_back({FreeListMutation::Pushed, tree_->freeNodeIds_[i]});
503 void restoreRollbackJournal() noexcept {
504 if (!active_ || tree_ ==
nullptr || !recoverable_) {
507 if (!rollbackJournal_) {
508 tree_->endEditSession();
509 recoverable_ =
false;
514 for (
auto it = rollbackJournal_->freeListMutations.rbegin(); it != rollbackJournal_->freeListMutations.rend(); ++it) {
515 if (it->mutation == FreeListMutation::Pushed) {
516 assert(!tree_->freeNodeIds_.empty());
517 assert(tree_->freeNodeIds_.back() == it->node);
518 tree_->freeNodeIds_.pop_back();
520 tree_->freeNodeIds_.push_back(it->node);
524 const std::size_t originalSlots = rollbackJournal_->originalNodeSlots;
525 tree_->nodeParent_.resize(originalSlots);
526 tree_->firstChild_.resize(originalSlots);
527 tree_->nextSibling_.resize(originalSlots);
528 tree_->prevSibling_.resize(originalSlots);
529 tree_->lastChild_.resize(originalSlots);
530 tree_->numChildrenByNode_.resize(originalSlots);
531 tree_->alive_.resize(originalSlots);
532 tree_->properHead_.resize(originalSlots);
533 tree_->properTail_.resize(originalSlots);
534 tree_->properPartCardinalityByNode_.resize(originalSlots);
536 for (
const NodeRollbackState& state : rollbackJournal_->nodes) {
537 const std::size_t slot =
static_cast<std::size_t
>(state.node);
538 tree_->nodeParent_[slot] = state.parent;
539 tree_->firstChild_[slot] = state.firstChild;
540 tree_->nextSibling_[slot] = state.nextSibling;
541 tree_->prevSibling_[slot] = state.prevSibling;
542 tree_->lastChild_[slot] = state.lastChild;
543 tree_->numChildrenByNode_[slot] = state.numChildren;
544 tree_->alive_[slot] = state.alive;
545 tree_->properHead_[slot] = state.properHead;
546 tree_->properTail_[slot] = state.properTail;
547 tree_->properPartCardinalityByNode_[slot] = state.properPartCardinality;
550 for (
const PixelRollbackState& state : rollbackJournal_->pixels) {
551 const std::size_t slot =
static_cast<std::size_t
>(state.pixel);
552 tree_->smallestNodeMap_[slot] = state.smallestNodeId;
553 tree_->nextProperPart_[slot] = state.next;
554 tree_->prevProperPart_[slot] = state.previous;
557 tree_->rootNodeId_ = rollbackJournal_->root;
558 tree_->numNodes_ = rollbackJournal_->numNodes;
559 tree_->preservedExternalNodeIdOffset_ = rollbackJournal_->preservedExternalNodeIdOffset;
560 tree_->nodeStructureVersion_ = rollbackJournal_->nodeStructureVersion;
561 tree_->topologyVersion_ = rollbackJournal_->topologyVersion;
562 tree_->properPartVersion_ = rollbackJournal_->properPartVersion;
563 tree_->mutationVersion_ = rollbackJournal_->mutationVersion;
564 tree_->invalidateDfsIntervalCache();
565 tree_->invalidateLcaCache();
566 tree_->endEditSession();
567 rollbackJournal_.reset();
568 recoverable_ =
false;
579 [[nodiscard]]
static bool isDetached(
const MorphologicalTree& tree, NodeId node)
noexcept {
580 return tree.isAlive(node) && node != tree.rootNodeId_ && tree.nodeParent_[
static_cast<std::size_t
>(node)] == node;
589 void recordDetachedTransition(
bool wasDetached,
bool isNowDetached)
noexcept {
590 if (invariantsEstablishedByConstruction_) {
593 detachedNodeBalance_ +=
static_cast<int>(isNowDetached) -
static_cast<int>(wasDetached);
603 [[nodiscard]]
static bool isUnsupportedLeaf(
const MorphologicalTree& tree, NodeId node)
noexcept {
604 return tree.isAlive(node) && tree.numChildrenByNode_[
static_cast<std::size_t
>(node)] == 0 &&
605 tree.properPartCardinalityByNode_[
static_cast<std::size_t
>(node)] == 0;
614 void recordUnsupportedLeafTransition(
bool wasUnsupported,
bool isNowUnsupported)
noexcept {
615 if (invariantsEstablishedByConstruction_) {
618 unsupportedLeafBalance_ +=
static_cast<int>(isNowUnsupported) -
static_cast<int>(wasUnsupported);
626 void touch(NodeId node) {
627 if (invariantsEstablishedByConstruction_) {
631 static_cast<void>(touchedNodes_.insert(node));
633 incrementalValidationSupported_ =
false;
654 : tree_(
other.tree_), active_(
other.active_), recoverable_(
other.recoverable_), rollbackJournal_(std::move(
other.rollbackJournal_)),
655 touchedNodes_(std::move(
other.touchedNodes_)), detachedNodeBalance_(
other.detachedNodeBalance_),
656 unsupportedLeafBalance_(
other.unsupportedLeafBalance_), incrementalValidationSupported_(
other.incrementalValidationSupported_),
657 invariantsEstablishedByConstruction_(
other.invariantsEstablishedByConstruction_) {
658 other.tree_ =
nullptr;
659 other.active_ =
false;
660 other.recoverable_ =
false;
661 other.rollbackJournal_.reset();
662 other.detachedNodeBalance_ = 0;
663 other.unsupportedLeafBalance_ = 0;
664 other.incrementalValidationSupported_ =
false;
665 other.invariantsEstablishedByConstruction_ =
false;
692 if (!active_ || tree_ ==
nullptr) {
693 throw std::logic_error(
"TreeEditor::rollback requires an active edit session.");
696 throw std::logic_error(
"TreeEditor::rollback is unavailable for the internal journal-free editor.");
698 restoreRollbackJournal();
711 if (invariantsEstablishedByConstruction_) {
712 return t.createDetachedNode();
715 ensureRollbackJournal();
720 rollbackJournal_->freeListMutations.reserve(rollbackJournal_->freeListMutations.size() + 1);
721 rollbackJournal_->freeListMutations.push_back({FreeListMutation::Popped,
candidate});
724 recordDetachedTransition(
false, isDetached(
t,
nodeId));
725 recordUnsupportedLeafTransition(
false, isUnsupportedLeaf(
t,
nodeId));
738 throw std::invalid_argument(
"TreeEditor::detach requires a live node.");
741 throw std::invalid_argument(
"TreeEditor::detach cannot detach the connected root.");
743 if (invariantsEstablishedByConstruction_) {
748 captureNodeLinkNeighborhood(
nodeId);
769 throw std::invalid_argument(
"TreeEditor::reparent requires live node ids.");
772 throw std::invalid_argument(
"TreeEditor::reparent cannot move the connected root.");
775 throw std::invalid_argument(
"TreeEditor::reparent requires distinct node ids.");
777 if (invariantsEstablishedByConstruction_) {
782 captureNodeLinkNeighborhood(
nodeId);
784 captureNodeForRollback(
t.lastChild_[
static_cast<std::size_t
>(
newParentId)]);
809 throw std::invalid_argument(
"TreeEditor::attach requires live node ids.");
812 throw std::invalid_argument(
"TreeEditor::attach requires distinct node ids.");
815 throw std::invalid_argument(
"TreeEditor::attach expects a detached self-parented node.");
817 if (invariantsEstablishedByConstruction_) {
823 captureNodeForRollback(
t.lastChild_[
static_cast<std::size_t
>(
parentId)]);
845 throw std::invalid_argument(
"TreeEditor::moveChildren requires live node ids.");
848 throw std::invalid_argument(
"TreeEditor::moveChildren requires distinct node ids.");
850 if (invariantsEstablishedByConstruction_) {
856 captureNodeForRollback(
t.lastChild_[
static_cast<std::size_t
>(
parentId)]);
858 captureNodeForRollback(
child);
881 throw std::invalid_argument(
"TreeEditor::movePixelToProperPart requires live node ids.");
884 throw std::invalid_argument(
"TreeEditor::movePixelToProperPart requires distinct source and target nodes.");
886 if (!
t.isPixel(pixel)) {
887 throw std::invalid_argument(
"TreeEditor::movePixelToProperPart requires a valid proper-part id.");
889 if (invariantsEstablishedByConstruction_) {
895 capturePixelLinkNeighborhood(pixel);
896 capturePixelForRollback(
t.properTail_[
static_cast<std::size_t
>(
targetNodeId)]);
916 throw std::invalid_argument(
"TreeEditor::mergeProperParts requires live node ids.");
919 throw std::invalid_argument(
"TreeEditor::mergeProperParts requires distinct source and target nodes.");
921 if (invariantsEstablishedByConstruction_) {
927 capturePixelForRollback(
t.properTail_[
static_cast<std::size_t
>(
targetNodeId)]);
929 pixel =
t.nextProperPart_[
static_cast<std::size_t
>(pixel)]) {
930 capturePixelForRollback(pixel);
949 throw std::invalid_argument(
"TreeEditor::removeChild requires live node ids.");
952 throw std::invalid_argument(
"TreeEditor::removeChild requires a direct parent-child relation.");
954 if (invariantsEstablishedByConstruction_) {
958 captureNodeLinkNeighborhood(
childId);
961 releaseNodeFlag &&
t.numChildrenByNode_[
static_cast<std::size_t
>(
childId)] == 0 &&
t.properPartCardinalityByNode_[
static_cast<std::size_t
>(
childId)] == 0;
963 prepareFreeListGrowth(1);
985 throw std::invalid_argument(
"TreeEditor::releaseNode requires a live node.");
988 throw std::invalid_argument(
"TreeEditor::releaseNode cannot release the connected root.");
991 throw std::invalid_argument(
"TreeEditor::releaseNode expects a detached self-parented node.");
993 if (invariantsEstablishedByConstruction_) {
997 captureNodeForRollback(
nodeId);
998 const bool willRelease =
t.numChildrenByNode_[
static_cast<std::size_t
>(
nodeId)] == 0 &&
t.properPartCardinalityByNode_[
static_cast<std::size_t
>(
nodeId)] == 0;
1000 prepareFreeListGrowth(1);
1022 throw std::invalid_argument(
"TreeEditor::setRoot requires a live node.");
1024 if (invariantsEstablishedByConstruction_) {
1030 captureNodeForRollback(
oldRoot);
1031 captureNodeLinkNeighborhood(
nodeId);
1052 if (invariantsEstablishedByConstruction_) {
1053 EditSessionPause
pause(
t);
1057 incrementalValidationSupported_ =
false;
1060 const NodeId parent =
t.nodeParent_[
static_cast<std::size_t
>(
nodeId)];
1064 for (std::size_t
i = 0;
i <
subtree.size(); ++
i) {
1067 child =
t.nextSibling_[
static_cast<std::size_t
>(
child)]) {
1072 captureNodeForRollback(parent);
1073 captureNodeLinkNeighborhood(
nodeId);
1074 capturePixelForRollback(
t.properTail_[
static_cast<std::size_t
>(parent)]);
1076 captureNodeForRollback(
current);
1078 pixel =
t.nextProperPart_[
static_cast<std::size_t
>(pixel)]) {
1079 capturePixelForRollback(pixel);
1082 prepareFreeListGrowth(
subtree.size());
1087 EditSessionPause
pause(
t);
1099 if (invariantsEstablishedByConstruction_) {
1100 EditSessionPause
pause(
t);
1101 t.mergeNodeIntoParent(
nodeId);
1104 incrementalValidationSupported_ =
false;
1107 const NodeId parent =
t.nodeParent_[
static_cast<std::size_t
>(
nodeId)];
1109 captureNodeForRollback(parent);
1110 captureNodeLinkNeighborhood(
nodeId);
1111 capturePixelForRollback(
t.properTail_[
static_cast<std::size_t
>(parent)]);
1113 pixel =
t.nextProperPart_[
static_cast<std::size_t
>(pixel)]) {
1114 capturePixelForRollback(pixel);
1117 child =
t.nextSibling_[
static_cast<std::size_t
>(
child)]) {
1118 captureNodeForRollback(
child);
1120 prepareFreeListGrowth(1);
1125 EditSessionPause
pause(
t);
1126 t.mergeNodeIntoParent(
nodeId);
1147 if (!active_ || tree_ ==
nullptr) {
1148 return {
false,
"Incremental topology validation requires an active edit session."};
1150 if (detachedNodeBalance_ != 0) {
1151 return {
false,
"Incremental topology validation found detached alive nodes."};
1153 if (unsupportedLeafBalance_ != 0) {
1154 return {
false,
"Incremental topology validation found a live node whose subtree support is empty."};
1157 const NodeId root = tree_->root();
1158 if (!tree_->isAlive(root) || tree_->parent(root) != root) {
1159 return {
false,
"Incremental topology validation requires a live self-parented root."};
1162 const int numNodes = tree_->numNodes();
1163 for (NodeId node : touchedNodes_.entries()) {
1164 if (!tree_->isAlive(node)) {
1168 if (!changedParentCyclesExcluded) {
1171 while (cursor != root) {
1172 if (!tree_->isAlive(cursor)) {
1173 return {
false,
"Incremental topology validation found a parent path outside the alive node domain."};
1175 const NodeId parent = tree_->parent(cursor);
1176 if (parent == InvalidNode || parent == cursor || !tree_->isAlive(parent)) {
1177 return {
false,
"Incremental topology validation found a detached or invalid parent path."};
1180 if (++pathLength > numNodes) {
1181 return {
false,
"Incremental topology validation found a parent cycle."};
1187 }
catch (
const std::exception& ex) {
1188 return {
false, ex.what()};
1190 return {
false,
"Incremental topology validation failed with an unknown error."};
1200 [[nodiscard]] IncrementalProof proveIncrementalImpl(
bool changedParentCyclesExcluded) {
1201 TreeValidationResult result;
1202 TreeEditValidationMode validationMode = TreeEditValidationMode::Incremental;
1203 if (invariantsEstablishedByConstruction_ && incrementalValidationSupported_) {
1204 result = {
true,
""};
1205 }
else if (incrementalValidationSupported_) {
1206 result = validateIncrementalTopology(changedParentCyclesExcluded);
1209 validationMode = TreeEditValidationMode::Complete;
1212 throw std::runtime_error(result.message);
1215 if (validationMode == TreeEditValidationMode::Incremental) {
1216 const TreeValidationResult oracle =
validate();
1218 throw std::runtime_error(std::string(
"Incremental topology proof disagrees with the complete validation oracle: ") + oracle.message);
1222 return IncrementalProof(*
this, validationMode);
1233 [[nodiscard]] IncrementalProof proveIncrementalWithStrictAltitudeAcyclicity() {
return proveIncrementalImpl(
true); }
1253 if (!active_ || tree_ ==
nullptr ||
proof.editor_ !=
this ||
proof.tree_ != tree_ ||
proof.mutationVersion_ != tree_->getMutationVersion()) {
1254 throw std::logic_error(
"Incremental topology proof is stale or belongs to another edit session.");
1258 proof.editor_ =
nullptr;
1259 proof.tree_ =
nullptr;
1269 if (!active_ || tree_ ==
nullptr) {
1270 return {
false,
"TreeEditor validation requires an active edit session."};
1272 return tree_->validateConnectedRootedTreeResult();
1285 finishCommit(TreeEditValidationMode::Complete);
1298 throw std::runtime_error(
result.message);