112 enum class Direction :
uint8_t { North = 0, East = 1, South = 2, West = 3 };
115 enum class TraceAdjacencyMode :
uint8_t { Dense, Sparse };
118 struct DirectedEdge {
122 int startVertex = -1;
126 Direction direction = Direction::North;
133 DirectedEdge() =
default;
144 DirectedEdge(
int packedEdge,
int startVertex,
int endVertex, Direction direction,
int signedArea2)
145 : packedEdge(packedEdge), startVertex(startVertex), endVertex(endVertex), direction(direction), signedArea2(signedArea2) {}
148#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
183 std::size_t treeMutationVersion_ = 0;
185 mutable LocalTraceDeltas localDeltas_;
188 mutable std::vector<int> cachedEdgeValues_;
190 mutable std::vector<uint32_t> cachedEdgeOffset_;
192 mutable std::vector<uint32_t> cachedEdgeSize_;
194 mutable std::vector<uint8_t> cachedEdgeReady_;
196 mutable std::size_t cachedEdgeReadyCount_ = 0;
199 mutable std::vector<ContourTraceLoop> cachedLoopInfos_;
201 mutable std::vector<uint32_t> cachedLoopInfoOffset_;
203 mutable std::vector<uint32_t> cachedLoopInfoSize_;
205 mutable std::vector<uint8_t> cachedLoopReady_;
207 mutable std::size_t cachedLoopReadyCount_ = 0;
209 mutable std::size_t cachedLoopEdgeCount_ = 0;
212 mutable std::vector<uint16_t> edgeMark_;
214 mutable uint16_t markGeneration_ = 1;
216 mutable bool edgeMaterializationScratchReleased_ =
false;
219 mutable std::vector<DirectedEdge> traceDirectedEdges_;
221 mutable std::vector<int> traceOutgoingHead_;
223 mutable std::vector<int> traceOutgoingNext_;
225 mutable std::vector<int> traceTouchedVertices_;
227 mutable std::vector<int> traceSparseVertexKeys_;
229 mutable std::vector<int> traceSparseOutgoingHead_;
231 mutable std::vector<uint32_t> traceSparseSlotGeneration_;
233 mutable std::vector<int> traceSparseTouchedSlots_;
235 mutable uint32_t traceSparseGeneration_ = 1;
237 mutable std::size_t nodeLocalLoopTraceCount_ = 0;
239 mutable std::vector<uint32_t> traceVisitedGeneration_;
241 mutable uint32_t traceVisitGeneration_ = 1;
243 mutable std::vector<int> traceNodeLoopEdges_;
245 mutable std::vector<ContourTraceLoop> traceNodeLoops_;
247 mutable bool traceScratchReleased_ =
false;
265 if (capacityHint > 0) {
266 cachedEdgeValues_.reserve(
static_cast<std::size_t
>(capacityHint));
303 iterator(
const std::vector<int>* values, std::size_t index) : values_(values), index_(index) {}
341 const std::vector<int>* values_ =
nullptr;
343 std::size_t index_ = 0;
358 EdgeRange(
const std::vector<int>* values, std::size_t offset, std::size_t
size) : values_(values), offset_(offset), size_(
size) {}
390 const std::vector<int>* values_ =
nullptr;
392 std::size_t offset_ = 0;
394 std::size_t size_ = 0;
397#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
401 struct StorageStats {
402 std::size_t addDeltaValues = 0;
403 std::size_t removeDeltaValues = 0;
411 std::size_t approxAllocatedBytes = 0;
414 [[
nodiscard]] StorageStats storageStats()
const {
415 requireStableTree(
"IncrementalContourTraces::storageStats");
417 stats.addDeltaValues = localDeltas_.addValues.size();
418 stats.removeDeltaValues = localDeltas_.removeValues.size();
419 stats.cachedEdgeValues = cachedEdgeValues_.size();
420 stats.cachedLoopEdges = cachedLoopEdgeCount_;
421 stats.cachedLoops = cachedLoopInfos_.size();
422 stats.cachedEdgeReadyNodes = cachedEdgeReadyCount_;
423 stats.cachedLoopReadyNodes = cachedLoopReadyCount_;
424 stats.traceDenseOutgoingSlots = traceOutgoingHead_.capacity();
425 stats.traceSparseOutgoingSlots = traceSparseVertexKeys_.capacity();
426 stats.approxAllocatedBytes =
427 localDeltas_.addValues.capacity() *
sizeof(
int) + localDeltas_.removeValues.capacity() *
sizeof(
int) +
428 localDeltas_.addSpans.capacity() *
sizeof(LocalTraceDeltas::Span) + localDeltas_.removeSpans.capacity() *
sizeof(LocalTraceDeltas::Span) +
429 cachedEdgeValues_.capacity() *
sizeof(
int) + cachedEdgeOffset_.capacity() *
sizeof(
uint32_t) + cachedEdgeSize_.capacity() *
sizeof(
uint32_t) +
431 cachedLoopInfoOffset_.capacity() *
sizeof(
uint32_t) + cachedLoopInfoSize_.capacity() *
sizeof(
uint32_t) +
432 cachedLoopReady_.capacity() *
sizeof(
uint8_t) + edgeMark_.capacity() *
sizeof(
uint16_t) +
433 traceDirectedEdges_.capacity() *
sizeof(DirectedEdge) + traceOutgoingHead_.capacity() *
sizeof(
int) +
434 traceOutgoingNext_.capacity() *
sizeof(
int) + traceTouchedVertices_.capacity() *
sizeof(
int) + traceSparseVertexKeys_.capacity() *
sizeof(
int) +
435 traceSparseOutgoingHead_.capacity() *
sizeof(
int) + traceSparseSlotGeneration_.capacity() *
sizeof(
uint32_t) +
436 traceSparseTouchedSlots_.capacity() *
sizeof(
int) + traceVisitedGeneration_.capacity() *
sizeof(
uint32_t) +
437 traceNodeLoopEdges_.capacity() *
sizeof(
int) + traceNodeLoops_.capacity() *
sizeof(
ContourTraceLoop);
449 requireStableTree(
"IncrementalContourTraces::getEdges");
450 requireLiveTraceNode(node,
"IncrementalContourTraces::getEdges");
451 ensureEdgesMaterialized(node);
452 return EdgeRange(&cachedEdgeValues_, cachedEdgeOffset_[
static_cast<std::size_t
>(node)], cachedEdgeSize_[
static_cast<std::size_t
>(node)]);
465 requireStableTree(
"IncrementalContourTraces::getLoops");
466 requireLiveTraceNode(node,
"IncrementalContourTraces::getLoops");
467 ensureNodeLoopsMaterialized(node);
468 const auto offset =
static_cast<std::size_t
>(cachedLoopInfoOffset_[
static_cast<std::size_t
>(node)]);
469 const auto size =
static_cast<std::size_t
>(cachedLoopInfoSize_[
static_cast<std::size_t
>(node)]);
473 return std::vector<ContourTraceLoop>(cachedLoopInfos_.begin() +
static_cast<std::ptrdiff_t
>(offset),
474 cachedLoopInfos_.begin() +
static_cast<std::ptrdiff_t
>(offset + size));
484 requireStableTree(
"IncrementalContourTraces::getLoopEdges");
485 const std::size_t offset =
loop.edgeOffset;
486 const std::size_t size =
loop.edgeCount;
487 if (offset > cachedEdgeValues_.size() || size > cachedEdgeValues_.size() - offset) {
488 throw std::invalid_argument(
"ContourTraceLoop does not belong to this contour-trace result.");
490 return EdgeRange(&cachedEdgeValues_, offset, size);
497 requireStableTree(
"IncrementalContourTraces::materializeAll");
498 ensureLoopsMaterialized(tree.
root());
507 requireStableTree(
"IncrementalContourTraces::isMaterialized");
509 if (!cachedLoopReady_[
static_cast<std::size_t
>(node)]) {
523 requireStableTree(
"IncrementalContourTraces::isEdgeMaterialized");
524 requireLiveTraceNode(node,
"IncrementalContourTraces::isEdgeMaterialized");
525 return static_cast<bool>(cachedEdgeReady_[
static_cast<std::size_t
>(node)]);
535 requireStableTree(
"IncrementalContourTraces::isNodeTraced");
536 requireLiveTraceNode(node,
"IncrementalContourTraces::isNodeTraced");
537 return static_cast<bool>(cachedLoopReady_[
static_cast<std::size_t
>(node)]);
540#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
542 requireStableTree(
"IncrementalContourTraces::profileMaterializeAllLoops");
547 ensureLoopsMaterialized(tree.
root());
552 activeTraceProfile_ = previousProfile;
563 void requireStableTree(
const char* context)
const { tree.
requireMutationVersion(treeMutationVersion_, context); }
571 void requireLiveTraceNode(NodeId node,
const char* context)
const {
573 throw std::invalid_argument(std::string(context) +
" requires a live internal NodeId.");
584 static uint32_t checkedU32(std::size_t value,
const char* context) {
585 if (value >
static_cast<std::size_t
>(std::numeric_limits<uint32_t>::max())) {
586 throw std::overflow_error(std::string(context) +
" exceeds uint32_t.");
588 return static_cast<uint32_t
>(value);
596 template <
class T>
static void releaseVector(std::vector<T>& values) { std::vector<T>().swap(values); }
598#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
599 using TraceProfileClock = std::chrono::steady_clock;
601 [[nodiscard]]
static TraceProfileClock::time_point traceProfileNow() {
return TraceProfileClock::now(); }
603 static std::int64_t traceProfileElapsedNs(TraceProfileClock::time_point start, TraceProfileClock::time_point end) {
604 return std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count();
613 [[nodiscard]]
bool allEdgesMaterialized()
const {
return cachedEdgeReadyCount_ ==
static_cast<std::size_t
>(tree.
numNodes()); }
620 [[nodiscard]]
bool allLoopsMaterialized()
const {
return cachedLoopReadyCount_ ==
static_cast<std::size_t
>(tree.
numNodes()); }
625 void releaseEdgeMaterializationScratchIfComplete()
const {
626 if (edgeMaterializationScratchReleased_ || !allEdgesMaterialized()) {
630 localDeltas_ = LocalTraceDeltas{};
631 releaseVector(edgeMark_);
633 edgeMaterializationScratchReleased_ =
true;
639 void releaseSparseTraceOutgoingScratch()
const {
640 releaseVector(traceSparseVertexKeys_);
641 releaseVector(traceSparseOutgoingHead_);
642 releaseVector(traceSparseSlotGeneration_);
643 releaseVector(traceSparseTouchedSlots_);
644 traceSparseGeneration_ = 1;
650 void releaseTraceScratchIfComplete()
const {
651 if (traceScratchReleased_ || !allLoopsMaterialized()) {
655 resetTraceOutgoingHeads();
656 releaseVector(traceDirectedEdges_);
657 releaseVector(traceOutgoingHead_);
658 releaseVector(traceOutgoingNext_);
659 releaseVector(traceTouchedVertices_);
660 releaseSparseTraceOutgoingScratch();
661 releaseVector(traceVisitedGeneration_);
662 releaseVector(traceNodeLoopEdges_);
663 releaseVector(traceNodeLoops_);
664 nodeLocalLoopTraceCount_ = 0;
665 traceVisitGeneration_ = 1;
666 traceScratchReleased_ =
true;
672 void nextMarkGeneration()
const {
674 if (markGeneration_ == 0) {
675 std::fill(edgeMark_.begin(), edgeMark_.end(), 0);
686 void addIfUnmarked(std::vector<int>& values,
int packedEdge)
const {
687 if (packedEdge < 0 || packedEdge >=
static_cast<int>(edgeMark_.size())) {
690 if (edgeMark_[
static_cast<std::size_t
>(packedEdge)] != markGeneration_) {
691 edgeMark_[
static_cast<std::size_t
>(packedEdge)] = markGeneration_;
692 values.push_back(packedEdge);
701 void removeIfMarked(
int packedEdge)
const {
702 if (packedEdge >= 0 && packedEdge <
static_cast<int>(edgeMark_.size())) {
703 edgeMark_[
static_cast<std::size_t
>(packedEdge)] = 0;
713 std::vector<int>::const_iterator cachedEdgeBegin(NodeId node)
const {
714 return cachedEdgeValues_.begin() +
static_cast<std::ptrdiff_t
>(cachedEdgeOffset_[
static_cast<std::size_t
>(node)]);
723 std::vector<int>::const_iterator cachedEdgeEnd(NodeId node)
const {
724 return cachedEdgeBegin(node) +
static_cast<std::ptrdiff_t
>(cachedEdgeSize_[
static_cast<std::size_t
>(node)]);
733 void commitMaterializedEdges(NodeId node,
const std::vector<int>& values)
const {
734 cachedEdgeOffset_[
static_cast<std::size_t
>(node)] = checkedU32(cachedEdgeValues_.size(),
"cached trace edge offset");
735 cachedEdgeSize_[
static_cast<std::size_t
>(node)] = checkedU32(values.size(),
"cached trace edge size");
736 cachedEdgeValues_.insert(cachedEdgeValues_.end(), values.begin(), values.end());
737 const auto index =
static_cast<std::size_t
>(node);
738 if (!cachedEdgeReady_[index]) {
739 cachedEdgeReady_[index] = 1;
740 ++cachedEdgeReadyCount_;
749 void ensureEdgesMaterialized(NodeId root)
const {
750 requireStableTree(
"IncrementalContourTraces::ensureEdgesMaterialized");
751 requireLiveTraceNode(root,
"IncrementalContourTraces::ensureEdgesMaterialized");
752 if (cachedEdgeReady_[
static_cast<std::size_t
>(root)]) {
756 std::vector<std::pair<NodeId, bool>> stack;
757 stack.emplace_back(root,
false);
758 std::vector<int> values;
760 while (!stack.empty()) {
761 const auto [node, expanded] = stack.back();
763 if (cachedEdgeReady_[
static_cast<std::size_t
>(node)]) {
767 stack.emplace_back(node,
true);
768 for (NodeId child : tree.children(node)) {
769 if (!cachedEdgeReady_[
static_cast<std::size_t
>(child)]) {
770 stack.emplace_back(child,
false);
777 const auto additions = localDeltas_.additions(node);
778 std::size_t reserveSize = additions.size();
779 for (NodeId child : tree.children(node)) {
780 reserveSize +=
static_cast<std::size_t
>(cachedEdgeSize_[
static_cast<std::size_t
>(child)]);
782 values.reserve(reserveSize);
783 nextMarkGeneration();
785 for (NodeId child : tree.children(node)) {
786 for (
auto it = cachedEdgeBegin(child); it != cachedEdgeEnd(child); ++it) {
787 addIfUnmarked(values, *it);
791 for (
int packedEdge : additions) {
792 addIfUnmarked(values, packedEdge);
795 for (
int packedEdge : localDeltas_.removals(node)) {
796 removeIfMarked(packedEdge);
799 std::size_t writeIndex = 0;
800 for (
int packedEdge : values) {
801 if (edgeMark_[
static_cast<std::size_t
>(packedEdge)] == markGeneration_) {
802 values[writeIndex++] = packedEdge;
805 values.resize(writeIndex);
806 commitMaterializedEdges(node, values);
808 releaseEdgeMaterializationScratchIfComplete();
819 static int vertexId(
int row,
int column,
int numVertexColumns) {
return (row * numVertexColumns) + column; }
822 struct OrientedGeometry {
824 int startVertex = -1;
828 Direction direction = Direction::North;
839 OrientedGeometry orientedGeometry(
int packedEdge)
const {
842 const int numVertexColumns = columns + 1;
846 case ContourTraceSide::North:
847 return {vertexId(row, column, numVertexColumns), vertexId(row, column + 1, numVertexColumns), Direction::East, -row};
848 case ContourTraceSide::East:
849 return {vertexId(row, column + 1, numVertexColumns), vertexId(row + 1, column + 1, numVertexColumns), Direction::South, column + 1};
850 case ContourTraceSide::South:
851 return {vertexId(row + 1, column + 1, numVertexColumns), vertexId(row + 1, column, numVertexColumns), Direction::West, row + 1};
852 case ContourTraceSide::West:
853 return {vertexId(row + 1, column, numVertexColumns), vertexId(row, column, numVertexColumns), Direction::North, -column};
855 throw std::runtime_error(
"Invalid contour trace side.");
865 static int turnPriority(Direction incoming, Direction outgoing) {
866 const int turn = (
static_cast<int>(outgoing) -
static_cast<int>(incoming) + 4) & 3;
867 static constexpr std::array<int, 4> priorities{
873 return priorities[
static_cast<std::size_t
>(turn)];
887 static int chooseNextEdge(
const DirectedEdge& current,
int outgoingHead,
const std::vector<DirectedEdge>& directedEdges,
888 const std::vector<int>& outgoingNext,
const std::vector<uint32_t>& visitedGeneration, uint32_t visitGeneration
889#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
891 std::size_t& candidateScans, std::size_t& visitedSkips, std::size_t& unvisitedCandidates
895 int bestPriority = std::numeric_limits<int>::max();
896 for (
int candidate = outgoingHead; candidate != -1; candidate = outgoingNext[
static_cast<std::size_t
>(candidate)]) {
897#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
900 if (visitedGeneration[
static_cast<std::size_t
>(candidate)] == visitGeneration) {
901#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
906#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
907 ++unvisitedCandidates;
909 const int priority = turnPriority(current.direction, directedEdges[
static_cast<std::size_t
>(candidate)].direction);
910 if (priority < bestPriority || (priority == bestPriority && directedEdges[
static_cast<std::size_t
>(candidate)].packedEdge <
911 directedEdges[
static_cast<std::size_t
>(best)].packedEdge)) {
913 bestPriority = priority;
922 void resetTraceOutgoingHeads()
const {
923 for (
int vertex : traceTouchedVertices_) {
924 const auto vertexIndex =
static_cast<std::size_t
>(vertex);
925 traceOutgoingHead_[vertexIndex] = -1;
927 traceTouchedVertices_.clear();
935 [[nodiscard]] std::size_t imageVertexCount()
const {
942 void ensureTraceOutgoingHeadStorage()
const {
943 if (!traceOutgoingHead_.empty()) {
946 traceOutgoingHead_.assign(imageVertexCount(), -1);
955 static std::size_t nextPowerOfTwoAtLeast(std::size_t value) {
956 std::size_t result = 1;
957 while (result < value) {
969 static std::size_t sparseOutgoingTableSize(std::size_t edgeCount) {
970 const std::size_t target = std::max<std::size_t>(2, (2 * edgeCount) + 1);
971 return nextPowerOfTwoAtLeast(target);
979 static constexpr std::size_t sparseNodeLocalTraceLimit() {
992 [[nodiscard]]
bool shouldUseSparseTraceAdjacency(std::size_t edgeCount,
bool streamLoopInfosDirectly)
const {
993 if (streamLoopInfosDirectly || !traceOutgoingHead_.empty()) {
996 if (nodeLocalLoopTraceCount_ >= sparseNodeLocalTraceLimit()) {
999 const std::size_t sparseBytes = sparseOutgoingTableSize(edgeCount) * ((2 *
sizeof(int)) +
sizeof(uint32_t));
1000 const std::size_t denseBytes = imageVertexCount() *
sizeof(int);
1001 return sparseBytes < denseBytes;
1009 void prepareSparseTraceOutgoingHeads(std::size_t edgeCount)
const {
1010 const std::size_t tableSize = sparseOutgoingTableSize(edgeCount);
1011 if (traceSparseVertexKeys_.size() != tableSize) {
1012 traceSparseVertexKeys_.assign(tableSize, 0);
1013 traceSparseOutgoingHead_.assign(tableSize, -1);
1014 traceSparseSlotGeneration_.assign(tableSize, 0);
1016 traceSparseTouchedSlots_.clear();
1018 ++traceSparseGeneration_;
1019 if (traceSparseGeneration_ == 0) {
1020 std::fill(traceSparseSlotGeneration_.begin(), traceSparseSlotGeneration_.end(), 0);
1021 traceSparseGeneration_ = 1;
1031 [[nodiscard]] std::size_t sparseTraceSlot(
int vertex)
const {
1032 const auto hash =
static_cast<uint32_t
>(vertex) * uint32_t{2654435761u};
1033 return static_cast<std::size_t
>(hash) & (traceSparseVertexKeys_.size() - 1);
1042 [[nodiscard]]
int sparseTraceOutgoingHead(
int vertex)
const {
1043 std::size_t slot = sparseTraceSlot(vertex);
1044 while (traceSparseSlotGeneration_[slot] == traceSparseGeneration_) {
1045 if (traceSparseVertexKeys_[slot] == vertex) {
1046 return traceSparseOutgoingHead_[slot];
1048 slot = (slot + 1) & (traceSparseVertexKeys_.size() - 1);
1059 [[nodiscard]] std::size_t findOrInsertSparseTraceVertex(
int vertex)
const {
1060 std::size_t slot = sparseTraceSlot(vertex);
1061 while (traceSparseSlotGeneration_[slot] == traceSparseGeneration_) {
1062 if (traceSparseVertexKeys_[slot] == vertex) {
1065 slot = (slot + 1) & (traceSparseVertexKeys_.size() - 1);
1068 traceSparseSlotGeneration_[slot] = traceSparseGeneration_;
1069 traceSparseVertexKeys_[slot] = vertex;
1070 traceSparseOutgoingHead_[slot] = -1;
1071 traceSparseTouchedSlots_.push_back(
static_cast<int>(slot));
1081 template <TraceAdjacencyMode Mode> [[nodiscard]]
int traceOutgoingHead(
int vertex)
const {
1082 if constexpr (
Mode == TraceAdjacencyMode::Sparse) {
1083 return sparseTraceOutgoingHead(vertex);
1085 return traceOutgoingHead_[
static_cast<std::size_t
>(vertex)];
1094 template <TraceAdjacencyMode Mode>
void prepareTraceAdjacency(std::size_t edgeCount)
const {
1095 if constexpr (
Mode == TraceAdjacencyMode::Sparse) {
1096 prepareSparseTraceOutgoingHeads(edgeCount);
1098 if (!traceSparseVertexKeys_.empty()) {
1099 releaseSparseTraceOutgoingScratch();
1101 ensureTraceOutgoingHeadStorage();
1102 resetTraceOutgoingHeads();
1109 template <TraceAdjacencyMode Mode>
void resetTraceAdjacency()
const {
1110 if constexpr (
Mode == TraceAdjacencyMode::Dense) {
1111 resetTraceOutgoingHeads();
1121 template <TraceAdjacencyMode Mode>
void pushTraceOutgoingEdge(
int startVertex,
int edgeIndex)
const {
1122 if constexpr (
Mode == TraceAdjacencyMode::Sparse) {
1123 const std::size_t slot = findOrInsertSparseTraceVertex(startVertex);
1124 traceOutgoingNext_.push_back(traceSparseOutgoingHead_[slot]);
1125 traceSparseOutgoingHead_[slot] = edgeIndex;
1127 const auto vertexIndex =
static_cast<std::size_t
>(startVertex);
1128 if (traceOutgoingHead_[vertexIndex] == -1) {
1129 traceTouchedVertices_.push_back(startVertex);
1131 traceOutgoingNext_.push_back(traceOutgoingHead_[vertexIndex]);
1132 traceOutgoingHead_[vertexIndex] = edgeIndex;
1141 void nextTraceVisitGeneration(std::size_t edgeCount)
const {
1142 if (traceVisitedGeneration_.size() < edgeCount) {
1143 traceVisitedGeneration_.resize(edgeCount, 0);
1145 ++traceVisitGeneration_;
1146 if (traceVisitGeneration_ == 0) {
1147 std::fill(traceVisitedGeneration_.begin(), traceVisitedGeneration_.end(), 0);
1148 traceVisitGeneration_ = 1;
1158 [[nodiscard]]
bool isTraceVisited(
int edgeIndex)
const {
return traceVisitedGeneration_[
static_cast<std::size_t
>(edgeIndex)] == traceVisitGeneration_; }
1165 void markTraceVisited(
int edgeIndex)
const { traceVisitedGeneration_[
static_cast<std::size_t
>(edgeIndex)] = traceVisitGeneration_; }
1172 void reserveLoopInfoCapacityForGlobalTrace(NodeId root)
const {
1173 std::size_t pendingNonEmptyNodes = 0;
1174 std::size_t pendingEdgeCount = 0;
1175 for (NodeId node : tree.subtreeNodes(root)) {
1176 if (cachedLoopReady_[
static_cast<std::size_t
>(node)]) {
1179 const auto edgeCount =
static_cast<std::size_t
>(cachedEdgeSize_[
static_cast<std::size_t
>(node)]);
1180 if (edgeCount == 0) {
1183 ++pendingNonEmptyNodes;
1184 pendingEdgeCount += edgeCount;
1186 if (pendingNonEmptyNodes == 0) {
1190 const std::size_t edgeBasedEstimate = std::max<std::size_t>(1, pendingEdgeCount / 16);
1191 const std::size_t additionalCapacity = std::max(pendingNonEmptyNodes, edgeBasedEstimate);
1192 const std::size_t targetCapacity = cachedLoopInfos_.size() + additionalCapacity;
1193 if (cachedLoopInfos_.capacity() < targetCapacity) {
1194 cachedLoopInfos_.reserve(targetCapacity);
1204 bool canReuseEdgeSegmentForLoops(NodeId node)
const {
1209 return parent ==
InvalidNode || parent == node || cachedEdgeReady_[
static_cast<std::size_t
>(parent)] != 0;
1217 void ensureNodeLoopsMaterialized(NodeId node)
const {
1218 requireStableTree(
"IncrementalContourTraces::ensureNodeLoopsMaterialized");
1219 requireLiveTraceNode(node,
"IncrementalContourTraces::ensureNodeLoopsMaterialized");
1220 if (cachedLoopReady_[
static_cast<std::size_t
>(node)]) {
1223 ensureEdgesMaterialized(node);
1224 traceNodeLoops(node,
false);
1232 void ensureLoopsMaterialized(NodeId root)
const {
1233 requireStableTree(
"IncrementalContourTraces::ensureLoopsMaterialized");
1234 requireLiveTraceNode(root,
"IncrementalContourTraces::ensureLoopsMaterialized");
1235 ensureEdgesMaterialized(root);
1236 reserveLoopInfoCapacityForGlobalTrace(root);
1238 std::vector<std::pair<NodeId, bool>> stack;
1239 stack.emplace_back(root,
false);
1241 while (!stack.empty()) {
1242 const auto [node, expanded] = stack.back();
1245 stack.emplace_back(node,
true);
1246 for (NodeId child : tree.children(node)) {
1247 if (!cachedLoopReady_[
static_cast<std::size_t
>(child)]) {
1248 stack.emplace_back(child,
false);
1253 if (!cachedLoopReady_[
static_cast<std::size_t
>(node)]) {
1254 traceNodeLoops(node,
true);
1265 void traceNodeLoops(NodeId node,
bool streamLoopInfosDirectly)
const {
1266 const std::size_t edgeCount =
static_cast<std::size_t
>(cachedEdgeSize_[
static_cast<std::size_t
>(node)]);
1267 const bool useSparseAdjacency = shouldUseSparseTraceAdjacency(edgeCount, streamLoopInfosDirectly);
1268 if (!streamLoopInfosDirectly) {
1269 ++nodeLocalLoopTraceCount_;
1271 if (useSparseAdjacency) {
1272 traceNodeLoopsWithAdjacency<TraceAdjacencyMode::Sparse>(node, streamLoopInfosDirectly, edgeCount);
1274 traceNodeLoopsWithAdjacency<TraceAdjacencyMode::Dense>(node, streamLoopInfosDirectly, edgeCount);
1285 template <TraceAdjacencyMode Mode>
void traceNodeLoopsWithAdjacency(NodeId node,
bool streamLoopInfosDirectly, std::size_t edgeCount)
const {
1286 prepareTraceAdjacency<Mode>(edgeCount);
1287#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1288 const auto buildAdjacencyStart = traceProfileNow();
1289 std::size_t nodeOutgoingVertices = 0;
1290 std::size_t nodeSingleOutgoingVertices = 0;
1291 std::size_t nodeMultiOutgoingVertices = 0;
1292 std::size_t nodeMaxOutgoingDegree = 0;
1293 std::size_t nodeClosedLoopStops = 0;
1294 std::size_t nodeMissingOutgoingStops = 0;
1295 std::size_t nodeSingleSuccessorSteps = 0;
1296 std::size_t nodeSingleSuccessorVisitedStops = 0;
1297 std::size_t nodeAmbiguousSuccessorSteps = 0;
1298 std::size_t nodeAmbiguousSuccessorDeadEnds = 0;
1299 std::size_t nodeSuccessorCandidateScans = 0;
1300 std::size_t nodeSuccessorVisitedSkips = 0;
1301 std::size_t nodeSuccessorUnvisitedCandidates = 0;
1303 traceDirectedEdges_.clear();
1304 traceDirectedEdges_.reserve(edgeCount);
1305 traceOutgoingNext_.clear();
1306 traceOutgoingNext_.reserve(edgeCount);
1308 for (
auto it = cachedEdgeBegin(node); it != cachedEdgeEnd(node); ++it) {
1309 const OrientedGeometry geometry = orientedGeometry(*it);
1310 const int edgeIndex =
static_cast<int>(traceDirectedEdges_.size());
1311 traceDirectedEdges_.emplace_back(*it, geometry.startVertex, geometry.endVertex, geometry.direction, geometry.signedArea2);
1312 pushTraceOutgoingEdge<Mode>(geometry.startVertex, edgeIndex);
1314#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1315 const auto buildAdjacencyEnd = traceProfileNow();
1316 const auto profileCountersStart = traceProfileNow();
1317 if constexpr (
Mode == TraceAdjacencyMode::Dense) {
1318 for (
int vertex : traceTouchedVertices_) {
1319 std::size_t degree = 0;
1320 for (
int candidate = traceOutgoingHead_[
static_cast<std::size_t
>(vertex)]; candidate != -1;
1321 candidate = traceOutgoingNext_[
static_cast<std::size_t
>(candidate)]) {
1327 ++nodeOutgoingVertices;
1329 ++nodeSingleOutgoingVertices;
1331 ++nodeMultiOutgoingVertices;
1333 nodeMaxOutgoingDegree = std::max(nodeMaxOutgoingDegree, degree);
1336 for (
int touchedSlot : traceSparseTouchedSlots_) {
1337 std::size_t degree = 0;
1338 const auto slot =
static_cast<std::size_t
>(touchedSlot);
1339 for (
int candidate = traceSparseOutgoingHead_[slot]; candidate != -1; candidate = traceOutgoingNext_[
static_cast<std::size_t
>(candidate)]) {
1345 ++nodeOutgoingVertices;
1347 ++nodeSingleOutgoingVertices;
1349 ++nodeMultiOutgoingVertices;
1351 nodeMaxOutgoingDegree = std::max(nodeMaxOutgoingDegree, degree);
1354 const auto profileCountersEnd = traceProfileNow();
1357 nextTraceVisitGeneration(traceDirectedEdges_.size());
1358 traceNodeLoopEdges_.clear();
1359 traceNodeLoopEdges_.reserve(traceDirectedEdges_.size());
1360 traceNodeLoops_.clear();
1361 if (!streamLoopInfosDirectly) {
1362 traceNodeLoops_.reserve((traceDirectedEdges_.size() / 4) + 1);
1365 std::size_t globalEdgeOffset =
static_cast<std::size_t
>(cachedEdgeOffset_[
static_cast<std::size_t
>(node)]);
1366 const bool reuseEdgeSegment = canReuseEdgeSegmentForLoops(node);
1367 if (!reuseEdgeSegment) {
1368 globalEdgeOffset = cachedEdgeValues_.size();
1370 const std::size_t loopInfoOffset = cachedLoopInfos_.size();
1372#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1373 const auto walkLoopsStart = traceProfileNow();
1376 for (
int startEdge = 0; startEdge < static_cast<int>(traceDirectedEdges_.size()); ++startEdge) {
1377 if (isTraceVisited(startEdge)) {
1381 const int loopStartVertex = traceDirectedEdges_[
static_cast<std::size_t
>(startEdge)].startVertex;
1382 const std::size_t loopEdgeOffset = traceNodeLoopEdges_.size();
1383 int signedArea2 = 0;
1384 int current = startEdge;
1388 while (current != -1) {
1389 const DirectedEdge& edge = traceDirectedEdges_[
static_cast<std::size_t
>(current)];
1390 markTraceVisited(current);
1391 traceNodeLoopEdges_.push_back(edge.packedEdge);
1392 signedArea2 += edge.signedArea2;
1394 if (edge.endVertex == loopStartVertex) {
1395#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1396 ++nodeClosedLoopStops;
1401 const int outgoingHead = traceOutgoingHead<Mode>(edge.endVertex);
1402 if (outgoingHead == -1) {
1403#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1404 ++nodeMissingOutgoingStops;
1408 if (traceOutgoingNext_[
static_cast<std::size_t
>(outgoingHead)] == -1) {
1409#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1410 ++nodeSingleSuccessorSteps;
1412 if (isTraceVisited(outgoingHead)) {
1413#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1414 ++nodeSingleSuccessorVisitedStops;
1418 current = outgoingHead;
1421#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1422 ++nodeAmbiguousSuccessorSteps;
1424 current = chooseNextEdge(edge, outgoingHead, traceDirectedEdges_, traceOutgoingNext_, traceVisitedGeneration_, traceVisitGeneration_
1425#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1427 nodeSuccessorCandidateScans, nodeSuccessorVisitedSkips, nodeSuccessorUnvisitedCandidates
1430#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1431 if (current == -1) {
1432 ++nodeAmbiguousSuccessorDeadEnds;
1438 const std::size_t loopEdgeCount = traceNodeLoopEdges_.size() - loopEdgeOffset;
1439 if (loopEdgeCount == 0) {
1443 const ContourLoopKind kind = signedArea2 >= 0 ? ContourLoopKind::External : ContourLoopKind::Internal;
1444 if (streamLoopInfosDirectly) {
1445 cachedLoopInfos_.push_back(ContourTraceLoop{kind, checkedU32(globalEdgeOffset + loopEdgeOffset,
"global loop edge offset"),
1446 checkedU32(loopEdgeCount,
"loop edge count"), signedArea2});
1448 traceNodeLoops_.push_back(ContourTraceLoop{kind, checkedU32(loopEdgeOffset,
"local loop edge offset"),
1449 checkedU32(loopEdgeCount,
"loop edge count"), signedArea2});
1453 if (streamLoopInfosDirectly) {
1454 cachedLoopInfos_.resize(loopInfoOffset);
1456 resetTraceAdjacency<Mode>();
1459#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1460 const auto walkLoopsEnd = traceProfileNow();
1461 const auto resetOutgoingStart = traceProfileNow();
1463 resetTraceAdjacency<Mode>();
1464#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1465 const auto resetOutgoingEnd = traceProfileNow();
1468 if (traceNodeLoopEdges_.size() != edgeCount) {
1469 if (streamLoopInfosDirectly) {
1470 cachedLoopInfos_.resize(loopInfoOffset);
1472 throw std::runtime_error(
"Contour trace loop traversal did not cover every materialized edge.");
1474#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1475 const auto commitEdgesStart = traceProfileNow();
1477 if (reuseEdgeSegment) {
1478 std::copy(traceNodeLoopEdges_.begin(), traceNodeLoopEdges_.end(), cachedEdgeValues_.begin() +
static_cast<std::ptrdiff_t
>(globalEdgeOffset));
1480 cachedEdgeValues_.insert(cachedEdgeValues_.end(), traceNodeLoopEdges_.begin(), traceNodeLoopEdges_.end());
1482#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1483 const auto commitEdgesEnd = traceProfileNow();
1484 const auto commitLoopsStart = traceProfileNow();
1486 cachedLoopEdgeCount_ += traceNodeLoopEdges_.size();
1488 if (!streamLoopInfosDirectly) {
1489 for (ContourTraceLoop& loop : traceNodeLoops_) {
1490 loop.edgeOffset = checkedU32(globalEdgeOffset + loop.edgeOffset,
"global loop edge offset");
1492 cachedLoopInfos_.insert(cachedLoopInfos_.end(), traceNodeLoops_.begin(), traceNodeLoops_.end());
1495 cachedLoopInfoOffset_[
static_cast<std::size_t
>(node)] = checkedU32(loopInfoOffset,
"loop info offset");
1496 cachedLoopInfoSize_[
static_cast<std::size_t
>(node)] = checkedU32(cachedLoopInfos_.size() - loopInfoOffset,
"loop info size");
1497 const auto index =
static_cast<std::size_t
>(node);
1498 if (!cachedLoopReady_[index]) {
1499 cachedLoopReady_[index] = 1;
1500 ++cachedLoopReadyCount_;
1502#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1503 const auto commitLoopsEnd = traceProfileNow();
1504 const auto releaseScratchStart = traceProfileNow();
1506 releaseTraceScratchIfComplete();
1507#ifdef MMCFILTERS_ENABLE_CONTOUR_TRACE_PROFILE
1508 const auto releaseScratchEnd = traceProfileNow();
1509 if (activeTraceProfile_ !=
nullptr) {
1510 activeTraceProfile_->nodesTraced += 1;
1511 activeTraceProfile_->edgesTraced += edgeCount;
1512 activeTraceProfile_->loopsTraced += cachedLoopInfos_.size() - loopInfoOffset;
1513 activeTraceProfile_->outgoingVertices += nodeOutgoingVertices;
1514 activeTraceProfile_->singleOutgoingVertices += nodeSingleOutgoingVertices;
1515 activeTraceProfile_->multiOutgoingVertices += nodeMultiOutgoingVertices;
1516 activeTraceProfile_->maxOutgoingDegree = std::max(activeTraceProfile_->maxOutgoingDegree, nodeMaxOutgoingDegree);
1517 activeTraceProfile_->closedLoopStops += nodeClosedLoopStops;
1518 activeTraceProfile_->missingOutgoingStops += nodeMissingOutgoingStops;
1519 activeTraceProfile_->singleSuccessorSteps += nodeSingleSuccessorSteps;
1520 activeTraceProfile_->singleSuccessorVisitedStops += nodeSingleSuccessorVisitedStops;
1521 activeTraceProfile_->ambiguousSuccessorSteps += nodeAmbiguousSuccessorSteps;
1522 activeTraceProfile_->ambiguousSuccessorDeadEnds += nodeAmbiguousSuccessorDeadEnds;
1523 activeTraceProfile_->successorCandidateScans += nodeSuccessorCandidateScans;
1524 activeTraceProfile_->successorVisitedSkips += nodeSuccessorVisitedSkips;
1525 activeTraceProfile_->successorUnvisitedCandidates += nodeSuccessorUnvisitedCandidates;
1526 activeTraceProfile_->profileCountersNs += traceProfileElapsedNs(profileCountersStart, profileCountersEnd);
1527 activeTraceProfile_->buildAdjacencyNs += traceProfileElapsedNs(buildAdjacencyStart, buildAdjacencyEnd);
1528 activeTraceProfile_->walkLoopsNs += traceProfileElapsedNs(walkLoopsStart, walkLoopsEnd);
1529 activeTraceProfile_->resetOutgoingNs += traceProfileElapsedNs(resetOutgoingStart, resetOutgoingEnd);
1530 activeTraceProfile_->commitEdgesNs += traceProfileElapsedNs(commitEdgesStart, commitEdgesEnd);
1531 activeTraceProfile_->commitLoopsNs += traceProfileElapsedNs(commitLoopsStart, commitLoopsEnd);
1532 activeTraceProfile_->releaseScratchNs += traceProfileElapsedNs(releaseScratchStart, releaseScratchEnd);