diff --git a/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotController.java b/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotController.java index 57822ecd5b..ef48e1ef6e 100644 --- a/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotController.java +++ b/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotController.java @@ -17,13 +17,12 @@ import java.time.Duration; import java.time.Instant; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Optional; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; public class WaterfallPlotController { @@ -309,7 +308,7 @@ public synchronized void redraw(WaterfallPlotRuntime.PVData pvData) { previousT2[0] = Optional.of(t2); LinkedList timeValuesLinkedList = new LinkedList<>(); - LinkedList> zValuesLinkedList = new LinkedList<>(); + LinkedList> zValuesLinkedList = new LinkedList<>(); int waveformLength = 1; double minFromPV = Double.NaN; @@ -320,21 +319,24 @@ public synchronized void redraw(WaterfallPlotRuntime.PVData pvData) { minFromPV = waveformPVData.minFromPV().get(); maxFromPV = waveformPVData.maxFromPV().get(); - TreeMap> instantToWaveform = waveformPVData.instantToValue().get(); + ConcurrentSkipListMap> instantToWaveform = waveformPVData.instantToValue(); + Instant startKey = instantToWaveform.ceilingKey(Instant.MIN); for (Instant t = t1.plus(stepsize); t.compareTo(t2) <= 0; t = t.plus(stepsize)) { timeValuesLinkedList.add(((double) t.toEpochMilli()) / 1000.0); - var instant = instantToWaveform.floorKey(t); - if (instant != null) { - LinkedList waveform = instantToWaveform.get(instant); + if (startKey == null || t.isBefore(startKey)) { + zValuesLinkedList.add(null); // null means absence of data for this point in time + } + else { + var instant = instantToWaveform.floorKey(t); + + ArrayList waveform = instantToWaveform.get(instant); waveformLength = Math.max(waveformLength, waveform.size()); zValuesLinkedList.add(waveform); - } else { - zValuesLinkedList.add(null); // null means absence of data for this point in time } } } else if (pvData instanceof WaterfallPlotRuntime.ScalarPVsData scalarPVsData) { - LinkedList>>> pvNameToInstantToValue = scalarPVsData.pvNameToInstantToValue(); + ArrayList>> pvNameToInstantToValue = scalarPVsData.pvNameToInstantToValue(); pvNameToInstantToValue.forEach(pvNameAndInstantToValueAtomicReference -> garbageCollectInstantToValue(pvNameAndInstantToValueAtomicReference.getValue(), t1)); minFromPV = scalarPVsData.minFromPV().get(); @@ -344,12 +346,12 @@ public synchronized void redraw(WaterfallPlotRuntime.PVData pvData) { for (Instant t = t1.plus(stepsize); t.compareTo(t2) <= 0; t = t.plus(stepsize)) { timeValuesLinkedList.add(((double) t.toEpochMilli()) / 1000.0); - LinkedList zValues = new LinkedList<>(); + ArrayList zValues = new ArrayList<>(); for (var pvNameAndInstantToValue : pvNameToInstantToValue) { String pvName = pvNameAndInstantToValue.getKey(); - TreeMap instantToValue = pvNameAndInstantToValue.getValue().get(); + ConcurrentSkipListMap instantToValue = pvNameAndInstantToValue.getValue(); var instant = instantToValue.floorKey(t); if (instant == null) { @@ -363,7 +365,7 @@ public synchronized void redraw(WaterfallPlotRuntime.PVData pvData) { // Append the last value one more time in order to // fix the plotting when there is only 1 scalar PV: - var lastValue = zValues.getLast(); + var lastValue = zValues.get(zValues.size()-1); zValues.add(lastValue); zValuesLinkedList.add(zValues); @@ -392,7 +394,7 @@ public synchronized void redraw(WaterfallPlotRuntime.PVData pvData) { } for (int n = 0; n < zValuesLinkedList.size(); n++) { - LinkedList waveformValues = zValuesLinkedList.get(n); + ArrayList waveformValues = zValuesLinkedList.get(n); for (int m = 0; m < waveformLength; m++) { double value; @@ -431,7 +433,7 @@ public synchronized void redraw(WaterfallPlotRuntime.PVData pvData) { } for (int n = 0; n < zValuesLinkedList.size(); n++) { - LinkedList waveformValues = zValuesLinkedList.get(n); + ArrayList waveformValues = zValuesLinkedList.get(n); for (int m = 0; m < waveformLength; m++) { double value; @@ -479,14 +481,13 @@ else if (zAxisMinMax.equals(WaterfallPlotWidget.ZAxisMinMax.FromPVLimits)) { } } - private synchronized static void garbageCollectInstantToValue(AtomicReference> instantToValueAtomicReference, Instant t1) { + private synchronized static void garbageCollectInstantToValue(ConcurrentSkipListMap instantToWaveform, Instant t1) { // Garbage collect old values that are no longer needed: - TreeMap instantToWaveform = instantToValueAtomicReference.get(); Instant instantOfOldestRelevantKey = instantToWaveform.floorKey(t1); if (instantOfOldestRelevantKey != null) { - SortedMap instantToWaveformGarbageCollectedSortedMap = instantToWaveform.subMap(instantOfOldestRelevantKey, Instant.MAX); - TreeMap instantToWaveformGarbageCollected = new TreeMap<>(instantToWaveformGarbageCollectedSortedMap); - instantToValueAtomicReference.set(instantToWaveformGarbageCollected); + for (var key : instantToWaveform.subMap(Instant.MIN, instantOfOldestRelevantKey).keySet()) { + instantToWaveform.remove(key); + } } } diff --git a/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotRuntime.java b/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotRuntime.java index 2e082c827a..e21e3eee42 100644 --- a/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotRuntime.java +++ b/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotRuntime.java @@ -22,10 +22,10 @@ import org.python.google.common.util.concurrent.AtomicDouble; import java.time.Instant; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.locks.Lock; import java.util.function.Consumer; import java.util.logging.Level; @@ -57,28 +57,25 @@ public void initialize(WaterfallPlotWidget waterfallPlotWidget) { if (isWaveform) { pvData = new WaveformPVData(new AtomicDouble(Double.NaN), new AtomicDouble(Double.NaN), - new AtomicReference<>(new TreeMap<>())); + new ConcurrentSkipListMap<>()); } else { pvData = new ScalarPVsData(new AtomicDouble(Double.NaN), new AtomicDouble(Double.NaN), - new LinkedList<>()); + new ArrayList<>()); } retrieveHistoricValuesFromTheArchiver = waterfallPlotWidget.propRetrieveHistoricValuesFromTheArchiver().getValue(); pvNames = waterfallPlotWidget.propInputPVs().getValue().stream().map(widgetProperty -> widgetProperty.getValue()).collect(Collectors.toUnmodifiableList()); } public sealed interface PVData permits WaveformPVData, ScalarPVsData {} - // In order to be able to garbage collect data points that are no longer needed, - // the TreeMap<> instantToValue is contained in an AtomicReference<>, so that we - // can set the map to a new map without values that are no longer needed. This is - // the case for both 'WaveformPVData' and 'ScalarPVsData'. + // The type ConcurrentSkipListMap is used for the data points to allow for concurrent insertions and deletions: public record WaveformPVData (AtomicDouble minFromPV, AtomicDouble maxFromPV, - AtomicReference>> instantToValue) implements PVData {} + ConcurrentSkipListMap> instantToValue) implements PVData {} public record ScalarPVsData (AtomicDouble minFromPV, AtomicDouble maxFromPV, - LinkedList>>> pvNameToInstantToValue) implements PVData {} + ArrayList>> pvNameToInstantToValue) implements PVData {} @Override public void start() { @@ -90,32 +87,28 @@ public void start() { try { RuntimePV runtimePV = PVFactory.getPV(pvName); super.addPV(runtimePV, false); - AtomicReference> instantToValueAtomicReference = new AtomicReference<>(new TreeMap<>()); - scalarPVsData.pvNameToInstantToValue.add(new Pair(pvName, instantToValueAtomicReference)); + ConcurrentSkipListMap instantToValue = new ConcurrentSkipListMap<>(); + scalarPVsData.pvNameToInstantToValue.add(new Pair(pvName, instantToValue)); runtimePV.addListener((pv, vType) -> { if (vType instanceof VNumber vnumber) { - synchronized (pvData) { - instantToValueAtomicReference.get().put(vnumber.getTime().getTimestamp(), vnumber.getValue().doubleValue()); - { - Range displayRange = vnumber.getDisplay().getDisplayRange(); - double minFromPV = displayRange.getMinimum(); - scalarPVsData.minFromPV.set(minFromPV); - double maxFromPV = displayRange.getMaximum(); - scalarPVsData.maxFromPV.set(maxFromPV); - } + instantToValue.put(vnumber.getTime().getTimestamp(), vnumber.getValue().doubleValue()); + { + Range displayRange = vnumber.getDisplay().getDisplayRange(); + double minFromPV = displayRange.getMinimum(); + scalarPVsData.minFromPV.set(minFromPV); + double maxFromPV = displayRange.getMaximum(); + scalarPVsData.maxFromPV.set(maxFromPV); } } else if (vType instanceof VEnum vEnum) { - synchronized (pvData) { - instantToValueAtomicReference.get().put(vEnum.getTime().getTimestamp(), (double) vEnum.getIndex()); - - { - int enumSize = vEnum.getDisplay().getChoices().size(); - double minFromPV = 0; - scalarPVsData.minFromPV.set(minFromPV); - double maxFromPV = enumSize - 1; - scalarPVsData.maxFromPV.set(maxFromPV); - } + instantToValue.put(vEnum.getTime().getTimestamp(), (double) vEnum.getIndex()); + + { + int enumSize = vEnum.getDisplay().getChoices().size(); + double minFromPV = 0; + scalarPVsData.minFromPV.set(minFromPV); + double maxFromPV = enumSize - 1; + scalarPVsData.maxFromPV.set(maxFromPV); } } else { @@ -128,18 +121,13 @@ else if (vType instanceof VEnum vEnum) { Instant.now().minusSeconds(timeSpanInSeconds), Instant.now(), values -> { - synchronized (pvData) { - for (var vtype : values) { - if (vtype instanceof VNumber vnumber) { - instantToValueAtomicReference.get().put(vnumber.getTime().getTimestamp(), vnumber.getValue().doubleValue()); - } - else if (vtype instanceof VStatistics vstatistics) { - instantToValueAtomicReference.get().put(vstatistics.getTime().getTimestamp(), vstatistics.getAverage()); - } - else if (vtype instanceof VEnum vEnum) { - instantToValueAtomicReference.get().put(vEnum.getTime().getTimestamp(), (double) vEnum.getIndex()); - } - + for (var vtype : values) { + if (vtype instanceof VNumber vnumber) { + instantToValue.put(vnumber.getTime().getTimestamp(), vnumber.getValue().doubleValue()); + } else if (vtype instanceof VStatistics vstatistics) { + instantToValue.put(vstatistics.getTime().getTimestamp(), vstatistics.getAverage()); + } else if (vtype instanceof VEnum vEnum) { + instantToValue.put(vEnum.getTime().getTimestamp(), (double) vEnum.getIndex()); } } }); @@ -157,43 +145,39 @@ else if (pvData instanceof WaveformPVData waveformPVData && pvNames.size() == 1) runtimePV.addListener((pv, vType) -> { if (vType instanceof VNumberArray vNumberArray) { - LinkedList waveform = new LinkedList<>(); + int size = vNumberArray.getData().size(); + ArrayList waveform = new ArrayList<>(size); for (int m = 0; m < vNumberArray.getData().size(); m++) { var value = vNumberArray.getData().getDouble(m); waveform.add(value); } - - synchronized (pvData) { - waveformPVData.instantToValue.get().put(vNumberArray.getTime().getTimestamp(), waveform); - - { - Range displayRange = vNumberArray.getDisplay().getDisplayRange(); - double minFromPV = displayRange.getMinimum(); - waveformPVData.minFromPV.set(minFromPV); - double maxFromPV = displayRange.getMaximum(); - waveformPVData.maxFromPV.set(maxFromPV); - } + waveformPVData.instantToValue.put(vNumberArray.getTime().getTimestamp(), waveform); + + { + Range displayRange = vNumberArray.getDisplay().getDisplayRange(); + double minFromPV = displayRange.getMinimum(); + waveformPVData.minFromPV.set(minFromPV); + double maxFromPV = displayRange.getMaximum(); + waveformPVData.maxFromPV.set(maxFromPV); } - } - else if (vType instanceof VEnumArray vEnumArray) { + } else if (vType instanceof VEnumArray vEnumArray) { - LinkedList waveform = new LinkedList<>(); + int size = vEnumArray.getData().size(); + ArrayList waveform = new ArrayList<>(size); ListNumber listNumber = vEnumArray.getIndexes(); for (int m = 0; m < vEnumArray.getData().size(); m++) { var value = listNumber.getDouble(m); waveform.add(value); } - synchronized (pvData) { - waveformPVData.instantToValue.get().put(vEnumArray.getTime().getTimestamp(), waveform); + waveformPVData.instantToValue.put(vEnumArray.getTime().getTimestamp(), waveform); - { - int enumSize = vEnumArray.getDisplay().getChoices().size(); - double minFromPV = 0; - waveformPVData.minFromPV.set(minFromPV); - double maxFromPV = enumSize - 1; - waveformPVData.maxFromPV.set(maxFromPV); - } + { + int enumSize = vEnumArray.getDisplay().getChoices().size(); + double minFromPV = 0; + waveformPVData.minFromPV.set(minFromPV); + double maxFromPV = enumSize - 1; + waveformPVData.maxFromPV.set(maxFromPV); } } }); @@ -207,24 +191,26 @@ else if (vType instanceof VEnumArray vEnumArray) { for (var vtype : values) { if (vtype instanceof VNumberArray vNumberArray) { - LinkedList waveform = new LinkedList<>(); + int size = vNumberArray.getData().size(); + ArrayList waveform = new ArrayList<>(size); for (int m = 0; m < vNumberArray.getData().size(); m++) { var value = vNumberArray.getData().getDouble(m); waveform.add(value); } - waveformPVData.instantToValue.get().put(vNumberArray.getTime().getTimestamp(), waveform); + waveformPVData.instantToValue.put(vNumberArray.getTime().getTimestamp(), waveform); } else if (vtype instanceof VEnumArray vEnumArray) { - LinkedList waveform = new LinkedList<>(); + int size = vEnumArray.getData().size(); + ArrayList waveform = new ArrayList<>(size); ListNumber listNumber = vEnumArray.getIndexes(); for (int m = 0; m < vEnumArray.getData().size(); m++) { var value = listNumber.getDouble(m); waveform.add(value); } - waveformPVData.instantToValue.get().put(vEnumArray.getTime().getTimestamp(), waveform); + waveformPVData.instantToValue.put(vEnumArray.getTime().getTimestamp(), waveform); } } } diff --git a/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotWidgetRepresentation.java b/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotWidgetRepresentation.java index d081b1bd0c..443c0c5a7e 100644 --- a/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotWidgetRepresentation.java +++ b/app/display/waterfallplot/src/main/java/org/phoebus/applications/waterfallplotwidget/WaterfallPlotWidgetRepresentation.java @@ -110,12 +110,10 @@ public WaterfallPlotWidgetRepresentation() { } WaterfallPlotRuntime.PVData pvData = waterfallPlotRuntime.getPVData(); - synchronized (pvData) { - try { - waterfallPlotController.redraw(pvData); - } catch (Exception e) { - // Catch exceptions in order to retry redrawing again if an exception occurs. - } + try { + waterfallPlotController.redraw(pvData); + } catch (Exception e) { + // Catch exceptions in order to retry redrawing again if an exception occurs. } };