Skip to content

Commit 140e602

Browse files
authored
Merge pull request #291 from saalfeldlab/fix-deadlock-at-startup
[BUGFIX] Fix deadlock at startup (#239 #288), thanks to @igorpisarev in #291
2 parents fa12cfd + eadf1fb commit 140e602

File tree

8 files changed

+82
-99
lines changed

8 files changed

+82
-99
lines changed

src/main/java/bdv/fx/viewer/ViewerPanelFX.java

+24-30
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@
2929
*/
3030
package bdv.fx.viewer;
3131

32+
import java.lang.invoke.MethodHandles;
33+
import java.util.ArrayList;
34+
import java.util.Collection;
35+
import java.util.List;
36+
import java.util.concurrent.CopyOnWriteArrayList;
37+
import java.util.concurrent.ExecutorService;
38+
import java.util.concurrent.Executors;
39+
import java.util.concurrent.ThreadFactory;
40+
import java.util.concurrent.atomic.AtomicInteger;
41+
import java.util.function.Function;
42+
43+
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
44+
import org.slf4j.Logger;
45+
import org.slf4j.LoggerFactory;
46+
3247
import bdv.cache.CacheControl;
3348
import bdv.fx.viewer.render.RenderUnit;
3449
import bdv.viewer.Interpolation;
@@ -39,7 +54,6 @@
3954
import javafx.beans.property.ReadOnlyBooleanProperty;
4055
import javafx.beans.property.ReadOnlyDoubleProperty;
4156
import javafx.collections.FXCollections;
42-
import javafx.collections.ListChangeListener;
4357
import javafx.collections.ObservableList;
4458
import javafx.scene.Node;
4559
import javafx.scene.layout.StackPane;
@@ -51,19 +65,6 @@
5165
import net.imglib2.RealPositionable;
5266
import net.imglib2.realtransform.AffineTransform3D;
5367
import net.imglib2.ui.TransformListener;
54-
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
55-
import org.slf4j.Logger;
56-
import org.slf4j.LoggerFactory;
57-
import java.lang.invoke.MethodHandles;
58-
import java.util.ArrayList;
59-
import java.util.Collection;
60-
import java.util.List;
61-
import java.util.concurrent.CopyOnWriteArrayList;
62-
import java.util.concurrent.ExecutorService;
63-
import java.util.concurrent.Executors;
64-
import java.util.concurrent.ThreadFactory;
65-
import java.util.concurrent.atomic.AtomicInteger;
66-
import java.util.function.Function;
6768

6869
/**
6970
* @author Philipp Hanslovsky
@@ -87,6 +88,7 @@ public class ViewerPanelFX
8788
private final OverlayPane<?> overlayPane = new OverlayPane<>();
8889

8990
private final ViewerState state;
91+
9092
private final AffineTransform3D viewerTransform;
9193

9294
private ThreadGroup threadGroup;
@@ -184,17 +186,11 @@ public ViewerPanelFX(
184186
this.renderingExecutorService = Executors.newFixedThreadPool(optional.values.getNumRenderingThreads(), new RenderThreadFactory());
185187
options = optional.values;
186188

187-
this.state = new ViewerState(axisOrder);
188-
189-
state.numTimepoints.set(numTimepoints);
190-
191189
threadGroup = new ThreadGroup(this.toString());
192190
viewerTransform = new AffineTransform3D();
193191

194192
transformListeners = new CopyOnWriteArrayList<>();
195193

196-
state.sourcesAndConverters.addListener((ListChangeListener<SourceAndConverter<?>>) c -> requestRepaint());
197-
198194
mouseTracker.installInto(this);
199195

200196
this.renderUnit = new RenderUnit(
@@ -214,9 +210,14 @@ public ViewerPanelFX(
214210
this.heightProperty().addListener((obs, oldv, newv) -> this.renderUnit.setDimensions((long)getWidth(), (long)getHeight()));
215211
setWidth(options.getWidth());
216212
setHeight(options.getHeight());
217-
setAllSources(sources);
213+
218214
// TODO why is this necessary?
219215
transformListeners.add(tf -> getDisplay().drawOverlays());
216+
217+
this.state = new ViewerState(axisOrder, numTimepoints);
218+
state.addListener(obs -> requestRepaint());
219+
220+
setAllSources(sources);
220221
}
221222

222223
/**
@@ -225,10 +226,7 @@ public ViewerPanelFX(
225226
*/
226227
public void setAllSources(final Collection<? extends SourceAndConverter<?>> sources)
227228
{
228-
synchronized (state)
229-
{
230-
this.state.sourcesAndConverters.setAll(sources);
231-
}
229+
this.state.setSources(sources);
232230
}
233231

234232
/**
@@ -350,13 +348,9 @@ public void requestRepaint(final long[] min, final long[] max)
350348
public synchronized void transformChanged(final AffineTransform3D transform)
351349
{
352350
viewerTransform.set(transform);
353-
synchronized (state)
354-
{
355-
state.setViewerTransform(transform);
356-
}
351+
state.setViewerTransform(transform);
357352
for (final TransformListener<AffineTransform3D> l : transformListeners)
358353
l.transformChanged(viewerTransform);
359-
requestRepaint();
360354
}
361355

362356
/**
+48-59
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,81 @@
11
package bdv.fx.viewer;
22

3+
import java.util.ArrayList;
4+
import java.util.Collection;
35
import java.util.Collections;
4-
import java.util.HashMap;
56
import java.util.List;
6-
import java.util.Map;
77
import java.util.function.Function;
8-
import java.util.stream.Collectors;
8+
9+
import org.janelia.saalfeldlab.fx.ObservableWithListenersList;
10+
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
911

1012
import bdv.util.MipmapTransforms;
1113
import bdv.viewer.Source;
1214
import bdv.viewer.SourceAndConverter;
13-
import javafx.beans.property.IntegerProperty;
14-
import javafx.beans.property.ReadOnlyIntegerProperty;
15-
import javafx.beans.property.SimpleIntegerProperty;
16-
import javafx.collections.FXCollections;
17-
import javafx.collections.ListChangeListener;
18-
import javafx.collections.ObservableList;
19-
import javafx.collections.ObservableMap;
2015
import net.imglib2.realtransform.AffineTransform3D;
21-
import org.janelia.saalfeldlab.paintera.data.axisorder.AxisOrder;
2216

23-
public class ViewerState
17+
public class ViewerState extends ObservableWithListenersList
2418
{
2519

2620
private final AffineTransform3D viewerTransform = new AffineTransform3D();
2721

28-
protected final IntegerProperty timepoint = new SimpleIntegerProperty(0);
22+
private final List<SourceAndConverter<?>> sourcesAndConverters = new ArrayList<>();
2923

30-
protected final IntegerProperty numTimepoints = new SimpleIntegerProperty(1);
24+
private final Function<Source<?>, AxisOrder> axisOrder;
3125

32-
protected final ObservableList<SourceAndConverter<?>> sourcesAndConverters = FXCollections.observableArrayList();
26+
private final int numTimepoints;
3327

34-
private final Function<Source<?>, AxisOrder> axisOrder;
28+
private int timepoint;
3529

36-
protected final ObservableMap<Source<?>, SourceAndConverter<?>> sources = asMap(
37-
sourcesAndConverters,
38-
SourceAndConverter::getSpimSource
39-
);
30+
public ViewerState(final Function<Source<?>, AxisOrder> axisOrder, final int numTimepoints)
31+
{
32+
this.axisOrder = axisOrder;
33+
this.numTimepoints = numTimepoints;
34+
}
4035

41-
protected synchronized void setViewerTransform(final AffineTransform3D to)
36+
protected void setViewerTransform(final AffineTransform3D to)
4237
{
43-
this.viewerTransform.set(to);
38+
synchronized (this)
39+
{
40+
this.viewerTransform.set(to);
41+
}
42+
stateChanged();
4443
}
4544

4645
public synchronized void getViewerTransform(final AffineTransform3D to)
4746
{
4847
to.set(this.viewerTransform);
4948
}
5049

51-
public ReadOnlyIntegerProperty timepointProperty()
50+
public void setTimepoint(final int timepoint)
51+
{
52+
synchronized (this)
53+
{
54+
this.timepoint = timepoint;
55+
}
56+
stateChanged();
57+
}
58+
59+
public synchronized int getTimepoint()
5260
{
5361
return this.timepoint;
5462
}
5563

56-
public List<SourceAndConverter<?>> getSources()
64+
public synchronized List<SourceAndConverter<?>> getSources()
5765
{
5866
return Collections.unmodifiableList(sourcesAndConverters);
5967
}
6068

69+
public void setSources(final Collection<? extends SourceAndConverter<?>> newSources)
70+
{
71+
synchronized (this)
72+
{
73+
this.sourcesAndConverters.clear();
74+
this.sourcesAndConverters.addAll(newSources);
75+
}
76+
stateChanged();
77+
}
78+
6179
public synchronized int getBestMipMapLevel(final AffineTransform3D screenScaleTransform, final Source<?> source,
6280
final int timepoint)
6381
{
@@ -70,54 +88,25 @@ public synchronized int getBestMipMapLevel(final AffineTransform3D screenScaleTr
7088

7189
public synchronized int getBestMipMapLevel(final AffineTransform3D screenScaleTransform, final Source<?> source)
7290
{
73-
return getBestMipMapLevel(screenScaleTransform, source, timepoint.get());
91+
return getBestMipMapLevel(screenScaleTransform, source, timepoint);
7492
}
7593

7694
public synchronized int getBestMipMapLevel(final AffineTransform3D screenScaleTransform, final int sourceIndex)
7795
{
7896
return getBestMipMapLevel(screenScaleTransform, sourcesAndConverters.get(sourceIndex).getSpimSource());
7997
}
8098

81-
public ViewerState(final Function<Source<?>, AxisOrder> axisOrder)
82-
{
83-
this.axisOrder = axisOrder;
84-
}
85-
8699
public synchronized ViewerState copy()
87100
{
88-
final ViewerState state = new ViewerState(this.axisOrder);
89-
state.viewerTransform.set(viewerTransform);
90-
state.timepoint.set(timepoint.get());
91-
state.numTimepoints.set(numTimepoints.get());
92-
state.sourcesAndConverters.setAll(sourcesAndConverters);
101+
final ViewerState state = new ViewerState(this.axisOrder, this.numTimepoints);
102+
state.setViewerTransform(this.viewerTransform);
103+
state.setTimepoint(this.timepoint);
104+
state.setSources(this.sourcesAndConverters);
93105
return state;
94106
}
95107

96-
public static <S, T> ObservableList<T> mapObservableList(final ObservableList<? extends S> source, final
97-
Function<S, T> mapping)
98-
{
99-
final ObservableList<T> target = FXCollections.observableArrayList();
100-
source.addListener((ListChangeListener<? super S>) change -> target.setAll(source.stream().map(mapping)
101-
.collect(
102-
Collectors.toList())));
103-
return target;
104-
}
105-
106-
public static <S, T> ObservableMap<T, S> asMap(final ObservableList<? extends S> source, final Function<S, T>
107-
generateKeyFromValue)
108-
{
109-
final ObservableMap<T, S> target = FXCollections.observableHashMap();
110-
source.addListener((ListChangeListener<? super S>) change -> {
111-
final Map<T, S> tmp = new HashMap<>();
112-
source.forEach(s -> tmp.put(generateKeyFromValue.apply(s), s));
113-
target.putAll(tmp);
114-
});
115-
return target;
116-
}
117-
118-
public AxisOrder axisOrder(final Source<?> source)
108+
public synchronized AxisOrder axisOrder(final Source<?> source)
119109
{
120110
return this.axisOrder.apply(source);
121111
}
122-
123112
}

src/main/java/bdv/fx/viewer/multibox/MultiBoxOverlayRendererFX.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public synchronized void setViewerState(final ViewerState viewerState)
153153
{
154154
synchronized (viewerState)
155155
{
156-
final int timepoint = viewerState.timepointProperty().get();
156+
final int timepoint = viewerState.getTimepoint();
157157

158158
final int numSources = this.allSources.size();
159159
final int numPresentSources = (int) IntStream.range(

src/main/java/bdv/fx/viewer/render/RenderUnit.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public void paint()
236236
synchronized (viewerState)
237237
{
238238
viewerState.getViewerTransform(viewerTransform);
239-
timepoint = viewerState.timepointProperty().get();
239+
timepoint = viewerState.getTimepoint();
240240
sacs.addAll(viewerState.getSources());
241241
}
242242
}

src/main/java/org/janelia/saalfeldlab/paintera/control/ShapeInterpolationMode.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ public boolean isModeOn()
378378

379379
private void createMask() throws MaskInUse
380380
{
381-
final int time = activeViewer.getState().timepointProperty().get();
381+
final int time = activeViewer.getState().getTimepoint();
382382
final int level = MASK_SCALE_LEVEL;
383383
final MaskInfo<UnsignedLongType> maskInfo = new MaskInfo<>(time, level, new UnsignedLongType(newLabelId));
384384
mask = source.generateMask(maskInfo, FOREGROUND_CHECK);
@@ -935,7 +935,7 @@ private UnsignedLongType getMaskValue(final double x, final double y)
935935
private D getDataValue(final double x, final double y)
936936
{
937937
final RealPoint sourcePos = getSourceCoordinates(x, y);
938-
final int time = activeViewer.getState().timepointProperty().get();
938+
final int time = activeViewer.getState().getTimepoint();
939939
final int level = MASK_SCALE_LEVEL;
940940
final RandomAccessibleInterval<D> data = source.getDataSource(time, level);
941941
final RandomAccess<D> dataAccess = data.randomAccess();
@@ -947,7 +947,7 @@ private D getDataValue(final double x, final double y)
947947
private AffineTransform3D getMaskTransform()
948948
{
949949
final AffineTransform3D maskTransform = new AffineTransform3D();
950-
final int time = activeViewer.getState().timepointProperty().get();
950+
final int time = activeViewer.getState().getTimepoint();
951951
final int level = MASK_SCALE_LEVEL;
952952
source.getSourceTransform(time, level, maskTransform);
953953
return maskTransform;
@@ -979,7 +979,7 @@ private AffineTransform3D getMaskDisplayTransformIgnoreScaling(final int targetL
979979
if (targetLevel != maskLevel)
980980
{
981981
// scale with respect to the given mipmap level
982-
final int time = activeViewer.getState().timepointProperty().get();
982+
final int time = activeViewer.getState().getTimepoint();
983983
final Scale3D relativeScaleTransform = new Scale3D(DataSource.getRelativeScales(source, time, maskLevel, targetLevel));
984984
maskMipmapDisplayTransform.preConcatenate(relativeScaleTransform.inverse());
985985
}
@@ -1000,7 +1000,7 @@ private AffineTransform3D getMaskDisplayTransformIgnoreScaling()
10001000
Arrays.setAll(viewerScale, d -> Affine3DHelpers.extractScale(viewerTransform, d));
10011001
final Scale3D scalingTransform = new Scale3D(viewerScale);
10021002
// neutralize mask scaling if there is any
1003-
final int time = activeViewer.getState().timepointProperty().get();
1003+
final int time = activeViewer.getState().getTimepoint();
10041004
final int level = MASK_SCALE_LEVEL;
10051005
scalingTransform.concatenate(new Scale3D(DataSource.getScale(source, time, level)));
10061006
// build the resulting transform

src/main/java/org/janelia/saalfeldlab/paintera/control/paint/FloodFill.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ private void fillAt(final double x, final double y, final long fill)
162162

163163
final int level = 0;
164164
final AffineTransform3D labelTransform = new AffineTransform3D();
165-
final int time = viewerState.timepointProperty().get();
165+
final int time = viewerState.getTimepoint();
166166
source.getSourceTransform(time, level, labelTransform);
167167

168168
final RealPoint rp = setCoordinates(x, y, viewer, labelTransform);

src/main/java/org/janelia/saalfeldlab/paintera/control/paint/FloodFill2D.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public <T extends IntegerType<T>> void fillAt(final double x, final double y, fi
162162
}
163163

164164
final int level = 0;
165-
final int time = viewerState.timepointProperty().get();
165+
final int time = viewerState.getTimepoint();
166166
final MaskInfo<UnsignedLongType> maskInfo = new MaskInfo<>(time, level, new UnsignedLongType(fill));
167167

168168
final Scene scene = viewer.getScene();

src/main/java/org/janelia/saalfeldlab/paintera/control/paint/RestrictPainting.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public void restrictTo(final double x, final double y)
137137
synchronized (viewerState)
138138
{
139139
level = viewerState.getBestMipMapLevel(screenScaleTransform, sourceInfo.currentSourceIndexInVisibleSources().get());
140-
time = viewerState.timepointProperty().get();
140+
time = viewerState.getTimepoint();
141141
}
142142
final AffineTransform3D labelTransform = new AffineTransform3D();
143143
source.getSourceTransform(time, level, labelTransform);

0 commit comments

Comments
 (0)