Skip to content

Commit 5af0117

Browse files
committed
DEV-896 Batch addPoint calls under one chart-monitor acquisition
Each ATrace2D.addPoint() takes synchronized(chart) - the same monitor Chart2D.paintComponent() holds - so plotting a multi-channel sample or an FFT frame grabbed the monitor once per point, repeatedly locking out the EDT. filterDataAndPlot now wraps its per-sample multi-trace loop in a single synchronized(mChart) (monitors are reentrant, so addPoint's inner lock is free); the hold is bounded by the trace count. Add addPointsToTrace(...) that adds many points to one trace under one lock in bounded 256-point chunks, and use it for the FFT bin loop. Event-marker, downsampling and per-sample filtering semantics are unchanged; BasicProcessWithCallBack is untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JjgNkS6wtPkQUdK3KdHVdN
1 parent 39c6fb4 commit 5af0117

1 file changed

Lines changed: 65 additions & 7 deletions

File tree

ShimmerDriverPC/src/main/java/com/shimmerresearch/guiUtilities/plot/BasicPlotManagerPC.java

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,56 @@ public void addPointToTrace(ITrace2D trace, double xData, double yData){
16451645
}
16461646
trace.addPoint(xData, yData);
16471647
}
1648-
1648+
1649+
/** DEV-896: Max points added per single chart-monitor acquisition in {@link #addPointsToTrace}.
1650+
* Bounds how long the batch can hold the chart lock so a large burst can't monopolise the EDT. */
1651+
private static final int POINT_BATCH_MAX = 256;
1652+
1653+
/**
1654+
* DEV-896: Batch variant of {@link #addPointToTrace(ITrace2D, double, double)}. Adds many
1655+
* points to a single trace while acquiring the chart monitor once per (bounded) chunk rather
1656+
* than once per point. {@code ATrace2D.addPoint()} synchronizes on the chart
1657+
* ({@code trace.getRenderer()}) - the same monitor {@code Chart2D.paintComponent()} holds - so
1658+
* adding N points individually took the monitor N times and starved the Swing EDT. Java monitors
1659+
* are reentrant, so {@code addPoint()}'s internal {@code synchronized(chart)} is free while we
1660+
* hold the outer lock. Behaviour per point is identical to {@link #addPointToTrace}.
1661+
*/
1662+
public void addPointsToTrace(ITrace2D trace, double[] xData, double[] yData){
1663+
if(xData == null || yData == null){
1664+
return;
1665+
}
1666+
addPointsToTrace(trace, xData, yData, 0, Math.min(xData.length, yData.length));
1667+
}
1668+
1669+
/**
1670+
* DEV-896: See {@link #addPointsToTrace(ITrace2D, double[], double[])}. Adds points
1671+
* {@code [fromIndex, toIndex)} from the given arrays.
1672+
*/
1673+
public void addPointsToTrace(ITrace2D trace, double[] xData, double[] yData, int fromIndex, int toIndex){
1674+
if(trace == null || xData == null || yData == null){
1675+
return;
1676+
}
1677+
int end = Math.min(toIndex, Math.min(xData.length, yData.length));
1678+
int i = Math.max(0, fromIndex);
1679+
//trace.getRenderer() returns the Chart2D the trace was added to (null until then); it is the
1680+
//exact monitor ATrace2D.addPoint() locks, so holding it makes the per-point locks reentrant.
1681+
Chart2D chart = trace.getRenderer();
1682+
while(i < end){
1683+
int chunkEnd = Math.min(i + POINT_BATCH_MAX, end);
1684+
if(chart != null){
1685+
synchronized(chart){
1686+
for(; i < chunkEnd; i++){
1687+
addPointToTrace(trace, xData[i], yData[i]);
1688+
}
1689+
}
1690+
} else {
1691+
for(; i < chunkEnd; i++){
1692+
addPointToTrace(trace, xData[i], yData[i]);
1693+
}
1694+
}
1695+
}
1696+
}
1697+
16491698
public CircularFifoBuffer getCirculurBufferedTraceData(String traceName){
16501699
CircularFifoBuffer circularFifoBuffer = mMapOfCirculurBufferedTraceDataPoints.get(traceName);
16511700
if(circularFifoBuffer != null){
@@ -1869,10 +1918,9 @@ public void run() {
18691918
if(results.length==2 && results[0].length>startBin){
18701919

18711920
trace.removeAllPoints();
1872-
1873-
for(int x=startBin;x<results[0].length;x++){
1874-
addPointToTrace(trace, results[0][x], results[1][x]);
1875-
}
1921+
1922+
//DEV-896: batch the FFT bins into one chart-monitor acquisition per chunk
1923+
addPointsToTrace(trace, results[0], results[1], startBin, results[0].length);
18761924
}
18771925
}
18781926

@@ -1951,8 +1999,17 @@ public void filterDataAndPlot(ObjectCluster ojc) throws Exception {
19511999
synchronized(mListofPropertiestoPlot){
19522000
Iterator <String[]> entries = mListofPropertiestoPlot.iterator();
19532001
int indexOfTrace = 0;
1954-
boolean isDummyPointAddedToFillTrace = false;
1955-
2002+
boolean isDummyPointAddedToFillTrace = false;
2003+
2004+
//DEV-896: Acquire the chart monitor once for this whole multi-trace update instead
2005+
//of once per trace inside ATrace2D.addPoint(). addPoint() synchronizes on the chart
2006+
//(trace.getRenderer()) - the same monitor Chart2D.paintComponent() holds - so grabbing
2007+
//it once per sample was starving the Swing EDT. Java monitors are reentrant, so the
2008+
//per-point synchronized(chart) inside addPoint() is free while we hold this outer lock.
2009+
//The batch is bounded by the number of plotted traces, so the lock hold stays short.
2010+
//Falls back to the already-held mListofPropertiestoPlot monitor if no chart is set yet.
2011+
Object chartMonitor = (mChart != null) ? (Object)mChart : (Object)mListofPropertiestoPlot;
2012+
synchronized(chartMonitor){
19562013
while (entries.hasNext()) {
19572014
String[] props = entries.next();
19582015

@@ -2067,6 +2124,7 @@ else if(isXAxisFrequency){
20672124
}
20682125
indexOfTrace++;
20692126
}
2127+
} //DEV-896: release the chart monitor once the whole multi-trace update is done
20702128
if(isDummyPointAddedToFillTrace) {
20712129
isFirstPointOnFillTrace = false;
20722130
}

0 commit comments

Comments
 (0)