Skip to content

Commit 8e8c73c

Browse files
committed
Fix Bvv <>Path Manager synchronization
1 parent 453c490 commit 8e8c73c

2 files changed

Lines changed: 25 additions & 56 deletions

File tree

‎src/main/java/sc/fiji/snt/viewer/AbstractBigViewer.java‎

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public abstract class AbstractBigViewer {
110110
protected final Map<String, Tree> renderedTrees = new LinkedHashMap<>();
111111

112112
/** Guards all access to {@link #renderedTrees} (and {@link #syncedPathManagerLabels}) */
113-
private final Object renderedTreesLock = new Object();
113+
final Object renderedTreesLock = new Object();
114114

115115
/**
116116
* Labels of the trees rendered by the last {@link #syncPathManagerList()} call. Needed because
@@ -168,31 +168,33 @@ public boolean syncPathManagerList() {
168168
if (snt == null)
169169
throw new IllegalArgumentException("Only available in SNT-tethered instances");
170170
final java.util.Collection<Tree> trees = snt.getPathAndFillManager().getTrees();
171-
if (trees.isEmpty()) {
172-
clearAllTrees();
173-
return false;
174-
}
175-
176-
final java.util.Set<String> currentLabels = trees.stream().map(Tree::getLabel)
177-
.collect(java.util.stream.Collectors.toSet());
178-
// See renderedTreesLock: this method can run concurrently with another instance of
179-
// itself (or addTree()/removeTree()/clearAllTrees()/getRenderedTrees()) on a different
180-
// background thread - guard every touch of renderedTrees/syncedPathManagerLabels
171+
// getTrees() builds each Tree from a label HashMap (PathAndFillManager#getTrees()), so within a single call
172+
// every Tree has a distinct, stable label - unlike renderedTrees' own keys, which are
173+
// getUniqueLabel()-disambiguated and can carry a "(n)" suffix from a genuinely-ambiguous historical add
174+
// (e.g. two files loaded with the same name). Removing and re-adding every tree on every sync routes new/kept
175+
// trees back through getUniqueLabel() and mints a brand new suffixed key even for an already-tracked tree
176+
// Here we match by bare label and update matching entries IN PLACE (same key, fresh Tree reference) so
177+
// unchanged trees are never removed from renderedTrees at all, are never touched by getUniqueLabel(), and
178+
// keep their cached screen geometry; only genuinely new trees go through addTree(), and only genuinely deleted
179+
// ones are removed
180+
final java.util.Map<String, Tree> newByLabel = new java.util.LinkedHashMap<>();
181+
for (final Tree t : trees) newByLabel.put(t.getLabel(), t);
181182
synchronized (renderedTreesLock) {
182-
// A tree that has been entirely deleted from the Path Manager no longer appears in getTrees() at all,
183-
// so it would never be matched by the "refresh existing labels" step. Prune it here by diffing against
184-
// what was last rendered
185-
final java.util.Set<String> stale = new java.util.HashSet<>(syncedPathManagerLabels);
186-
stale.removeAll(currentLabels);
187-
stale.forEach(renderedTrees.keySet()::remove);
188-
// Force a refresh of the trees that do still exist, so edits (nodes, color, selection) are picked up
189-
// rather than just additions/removals
190-
currentLabels.forEach(renderedTrees.keySet()::remove);
183+
final java.util.Iterator<java.util.Map.Entry<String, Tree>> it = renderedTrees.entrySet().iterator();
184+
while (it.hasNext()) {
185+
final java.util.Map.Entry<String, Tree> entry = it.next();
186+
final Tree replacement = newByLabel.remove(entry.getValue().getLabel());
187+
if (replacement != null) entry.setValue(replacement); // same key: reuse, don't re-add
188+
else it.remove(); // no longer in the Path Manager
189+
}
190+
// Whatever remains in newByLabel was never rendered before: genuinely new trees
191+
newByLabel.values().forEach(t -> addTree(t, false)); // reentrant lock, safe to call here
191192
syncedPathManagerLabels.clear();
192-
syncedPathManagerLabels.addAll(currentLabels);
193+
syncedPathManagerLabels.addAll(trees.stream().map(Tree::getLabel)
194+
.collect(java.util.stream.Collectors.toSet()));
193195
}
194-
addCollection(trees, true);
195-
return true;
196+
syncOverlays();
197+
return !trees.isEmpty();
196198
}
197199

198200
/**

‎src/main/java/sc/fiji/snt/viewer/Bvv.java‎

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,39 +2684,6 @@ public void updateSecondaryLayerIndicator() {
26842684
if (SwingUtilities.isEventDispatchThread()) update.run(); else SwingUtilities.invokeLater(update);
26852685
}
26862686

2687-
/**
2688-
* Synchronizes the Path Manager contents with BVV display.
2689-
*
2690-
* @return true if synchronization was successful
2691-
* @throws IllegalArgumentException if this is a standalone viewer not tethered to a SNT instance
2692-
*/
2693-
@Override
2694-
public boolean syncPathManagerList() {
2695-
if (snt == null)
2696-
throw new IllegalArgumentException("This function is only available in snt-aware Bvv instances");
2697-
final Collection<Tree> trees = snt.getPathAndFillManager().getTrees();
2698-
final Set<String> currentLabels = trees.stream().map(Tree::getLabel)
2699-
.collect(java.util.stream.Collectors.toSet());
2700-
// A tree that has been entirely deleted from the Path Manager (every one of its paths
2701-
// removed) no longer appears in getTrees() at all, so it would never be matched by the
2702-
// "refresh existing labels" step below. Prune it here by diffing against what we rendered
2703-
// last time instead, otherwise it lingers in the BVV scene forever.
2704-
final Set<String> stale = new HashSet<>(syncedPathManagerLabels);
2705-
stale.removeAll(currentLabels);
2706-
stale.forEach(renderedTrees.keySet()::remove);
2707-
// Force a refresh of the trees that do still exist, so edits (nodes, color, selection) are
2708-
// picked up rather than just additions/removals.
2709-
currentLabels.forEach(renderedTrees.keySet()::remove);
2710-
syncedPathManagerLabels.clear();
2711-
syncedPathManagerLabels.addAll(currentLabels);
2712-
if (trees.isEmpty()) {
2713-
syncOverlays(); // still need to push the removal above to the screen
2714-
return false;
2715-
}
2716-
addCollection(trees, true);
2717-
return true;
2718-
}
2719-
27202687
/**
27212688
* Lightweight alternative to {@link #syncPathManagerList()} for pure selection changes. Patches
27222689
* color/thickness in place for already-rendered trees instead of rebuilding the Path Manager's

0 commit comments

Comments
 (0)