From d0ac3ca615b0694f1607131d57fb53ecdedfde9b Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 3 Nov 2020 00:17:04 -0500 Subject: [PATCH 1/4] Fix potential concurrent modification exception --- src/uorocketry/basestation/Main.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uorocketry/basestation/Main.java b/src/uorocketry/basestation/Main.java index ce8b137..faafc4c 100644 --- a/src/uorocketry/basestation/Main.java +++ b/src/uorocketry/basestation/Main.java @@ -396,7 +396,7 @@ public void updateUI() { } // Update every chart - for (DataChart chart : window.charts) { + for (DataChart chart : new ArrayList(window.charts)) { updateChart(chart); } } From 82d8ff49b255ec2dff822d69f0ae90239ad943cd Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 4 Nov 2020 01:34:16 -0500 Subject: [PATCH 2/4] Update UI in another thread --- src/uorocketry/basestation/Main.java | 100 ++++++++++++++-------- src/uorocketry/basestation/SnapPanel.java | 75 ++++++++-------- 2 files changed, 104 insertions(+), 71 deletions(-) diff --git a/src/uorocketry/basestation/Main.java b/src/uorocketry/basestation/Main.java index faafc4c..1d1cda0 100644 --- a/src/uorocketry/basestation/Main.java +++ b/src/uorocketry/basestation/Main.java @@ -21,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -103,6 +104,9 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, /** If true, slider will temporarily stop growing */ boolean paused = false; + /** Used to only update the UI once at a time, even though it runs in its own thread */ + boolean updatingUI = false; + /** If not in a simulation, the serial ports being listened to */ List activeSerialPorts = new ArrayList(2); @@ -353,10 +357,12 @@ public void setupUI() { // Setup Snap Panel system - selectedChart = window.charts.get(0); - selectedChart.snapPanel.setSnapPanelListener(this); - - snapPanelSelected(selectedChart.snapPanel); + synchronized (window.charts) { + selectedChart = window.charts.get(0); + selectedChart.snapPanel.setSnapPanelListener(this); + + snapPanelSelected(selectedChart.snapPanel); + } } public void setupGoogleEarth() { @@ -368,37 +374,53 @@ public void setupGoogleEarth() { public void updateUI() { // If not ready yet - if (allData.size() == 0) return; + if (allData.size() == 0 || updatingUI) return; - // Update every table's data - for (int i = 0; i < allData.size(); i++) { - // If not ready yet - if (allData.get(i).size() == 0) continue; - - // Don't change slider if paused - if (!paused) { - // Set max value of the sliders - window.maxSliders.get(i).setMaximum(allData.get(i).size() - 1); - window.minSliders.get(i).setMaximum(allData.get(i).size() - 1); + updatingUI = true; + + // Update UI on another thread + new Thread(this::updateUIInternal).start(); + } + + private void updateUIInternal() { + try { + // Update every table's data + for (int i = 0; i < allData.size(); i++) { + // If not ready yet + if (allData.get(i).size() == 0) continue; + + // Don't change slider if paused + if (!paused) { + // Set max value of the sliders + window.maxSliders.get(i).setMaximum(allData.get(i).size() - 1); + window.minSliders.get(i).setMaximum(allData.get(i).size() - 1); + } + + DataHandler currentDataHandler = allData.get(i).get(currentDataIndexes.get(i)); + + if (currentDataHandler != null) { + currentDataHandler.updateTableUIWithData(window.dataTables.get(i), labels.get(i)); + } else { + setTableToError(i, window.dataTables.get(i)); + } } - DataHandler currentDataHandler = allData.get(i).get(currentDataIndexes.get(i)); + if (googleEarth) { + googleEarthUpdater.updateKMLFile(allData, minDataIndexes, currentDataIndexes, config.getJSONArray("datasets"), false); + } - if (currentDataHandler != null) { - currentDataHandler.updateTableUIWithData(window.dataTables.get(i), labels.get(i)); - } else { - setTableToError(i, window.dataTables.get(i)); + // Update every chart + synchronized (window.charts) { + for (DataChart chart : window.charts) { + updateChart(chart); + } } + } catch (Exception e) { + // Don't let an exception while updating break the program + e.printStackTrace(); } - if (googleEarth) { - googleEarthUpdater.updateKMLFile(allData, minDataIndexes, currentDataIndexes, config.getJSONArray("datasets"), false); - } - - // Update every chart - for (DataChart chart : new ArrayList(window.charts)) { - updateChart(chart); - } + updatingUI = false; } public void setTableToError(int index, JTable table) { @@ -639,10 +661,10 @@ public void stateChanged(ChangeEvent e) { window.minSliders.get(tableIndex).setValue(minDataIndexes.get(tableIndex)); } - updateUI(); - // Update the latest value - latest = currentDataIndexes.get(tableIndex) == maxSlider.getMaximum() - 1; + latest = maxSlider.getValue() == maxSlider.getMaximum() - 1; + + updateUI(); } else if (e.getSource() instanceof JSlider && window.minSliders.contains(e.getSource())) { JSlider minSlider = (JSlider) e.getSource(); int tableIndex = window.minSliders.indexOf(minSlider); @@ -935,7 +957,9 @@ public void addChart(boolean silent) { dataChart.snapPanel.setRelSize(600, 450); // Add these default charts to the list - window.charts.add(dataChart); + synchronized (window.charts) { + window.charts.add(dataChart); + } // Set to be selected window.centerChartPanel.setComponentZOrder(chartPanel, 0); @@ -998,8 +1022,10 @@ public void run() { formattedSelections[j] = new DataType(selections[j], window.dataTables.indexOf(dataTable)); } - // Set chart to be based on this row - selectedChart.xTypes = formattedSelections; + synchronized (window.charts) { + // Set chart to be based on this row + selectedChart.xTypes = formattedSelections; + } dataTable.setColumnSelectionInterval(0, 0); @@ -1134,8 +1160,10 @@ public void componentResized(ComponentEvent e) { int currentChartContainerWidth = window.centerChartPanel.getWidth(); int currentChartContainerHeight = window.centerChartPanel.getHeight(); - for (DataChart chart : window.charts) { - chart.snapPanel.containerResized(currentChartContainerWidth, currentChartContainerHeight); + synchronized (window.charts) { + for (DataChart chart : window.charts) { + chart.snapPanel.containerResized(currentChartContainerWidth, currentChartContainerHeight); + } } chartContainerWidth = currentChartContainerWidth; diff --git a/src/uorocketry/basestation/SnapPanel.java b/src/uorocketry/basestation/SnapPanel.java index a6b4b6c..d22adc2 100644 --- a/src/uorocketry/basestation/SnapPanel.java +++ b/src/uorocketry/basestation/SnapPanel.java @@ -66,8 +66,11 @@ public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON2) { // Close this - chart.main.window.charts.remove(chart); + synchronized (chart.main.window.charts) { + chart.main.window.charts.remove(chart); + } chart.main.window.centerChartPanel.remove(panel); + } lastClickTime = System.nanoTime(); @@ -136,46 +139,48 @@ public DataChart findClosestChart(int x, int y, int direction, int coordinate) { otherPos = x; } - for (DataChart chart : chart.main.window.charts) { - if (chart == this.chart) continue; - - int currentChartPos = (int) chart.chartPanel.getBounds().getX(); - int currentChartOtherPos = (int) chart.chartPanel.getBounds().getY(); - int currentChartSize = (int) chart.chartPanel.getBounds().getWidth(); - int currentChartOtherSize = (int) chart.chartPanel.getBounds().getHeight(); - int closestChartPos = 0; - int closestChartSize = 0; - // To prevent null pointer exceptions - if (closestChart != null) { - closestChartPos = (int) closestChart.chartPanel.getBounds().getX(); - closestChartSize = (int) closestChart.chartPanel.getBounds().getWidth(); - } - - // For Y coordinate - if (coordinate == 1) { - currentChartPos = (int) chart.chartPanel.getBounds().getY(); - currentChartOtherPos = (int) chart.chartPanel.getBounds().getX(); - currentChartSize = (int) chart.chartPanel.getBounds().getHeight(); - currentChartOtherSize = (int) chart.chartPanel.getBounds().getWidth(); + synchronized (chart.main.window.charts) { + for (DataChart chart : chart.main.window.charts) { + if (chart == this.chart) continue; + int currentChartPos = (int) chart.chartPanel.getBounds().getX(); + int currentChartOtherPos = (int) chart.chartPanel.getBounds().getY(); + int currentChartSize = (int) chart.chartPanel.getBounds().getWidth(); + int currentChartOtherSize = (int) chart.chartPanel.getBounds().getHeight(); + int closestChartPos = 0; + int closestChartSize = 0; + // To prevent null pointer exceptions if (closestChart != null) { - closestChartPos = (int) closestChart.chartPanel.getBounds().getY(); - closestChartSize = (int) closestChart.chartPanel.getBounds().getHeight(); + closestChartPos = (int) closestChart.chartPanel.getBounds().getX(); + closestChartSize = (int) closestChart.chartPanel.getBounds().getWidth(); } - } - - if (currentChartOtherPos < otherPos && currentChartOtherPos + currentChartOtherSize > otherPos) { - // Check for direction == 0 - boolean check = currentChartPos < pos && - (closestChart == null || closestChartPos < currentChartPos); - if (direction == 1) { - check = currentChartPos > pos && - (closestChart == null || closestChartPos > currentChartPos); + // For Y coordinate + if (coordinate == 1) { + currentChartPos = (int) chart.chartPanel.getBounds().getY(); + currentChartOtherPos = (int) chart.chartPanel.getBounds().getX(); + currentChartSize = (int) chart.chartPanel.getBounds().getHeight(); + currentChartOtherSize = (int) chart.chartPanel.getBounds().getWidth(); + + if (closestChart != null) { + closestChartPos = (int) closestChart.chartPanel.getBounds().getY(); + closestChartSize = (int) closestChart.chartPanel.getBounds().getHeight(); + } } - if (check) { - closestChart = chart; + if (currentChartOtherPos < otherPos && currentChartOtherPos + currentChartOtherSize > otherPos) { + // Check for direction == 0 + boolean check = currentChartPos < pos && + (closestChart == null || closestChartPos < currentChartPos); + + if (direction == 1) { + check = currentChartPos > pos && + (closestChart == null || closestChartPos > currentChartPos); + } + + if (check) { + closestChart = chart; + } } } } From d3cb1f5a69557039ba42b268cc42f4a2261ca302 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 4 Nov 2020 02:18:41 -0500 Subject: [PATCH 3/4] Added max number of data points displayed --- src/uorocketry/basestation/Main.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/uorocketry/basestation/Main.java b/src/uorocketry/basestation/Main.java index 1d1cda0..27eda67 100644 --- a/src/uorocketry/basestation/Main.java +++ b/src/uorocketry/basestation/Main.java @@ -83,6 +83,9 @@ public class Main implements ComponentListener, ChangeListener, ActionListener, /** Will have a number appended to the end to not overwrite old logs */ ArrayList currentLogFileName = new ArrayList(2); + /** Used to limit displayed data points to speed up rendering */ + public static int maxDataPointsDisplayed = 1000; + /** How many data sources to record data from. It is set when the config is loaded. */ public static int dataSourceCount = 1; @@ -467,14 +470,22 @@ public void updateChart(DataChart chart) { // Add x axis for (int i = 0; i < chart.xTypes.length; i++) { + // Used to limit the max number of data points displayed + float targetRatio = (float) maxDataPointsDisplayed / (currentDataIndexes.get(chart.xTypes[i].tableIndex) - minDataIndexes.get(chart.xTypes[i].tableIndex)); + int dataPointsAdded = 0; + for (int j = minDataIndexes.get(chart.xTypes[i].tableIndex); j <= currentDataIndexes.get(chart.xTypes[i].tableIndex); j++) { if (allData.get(chart.yType.tableIndex).size() == 0) continue; DataHandler data = allData.get(chart.xTypes[i].tableIndex).get(j); if (data != null) { - if (!data.hiddenDataTypes.contains(data.types[chart.xTypes[i].index])) { + // Ensures that not too many data points are displayed + boolean shouldShowDataPoint = (float) dataPointsAdded / j <= targetRatio; + + if (!data.hiddenDataTypes.contains(data.types[chart.xTypes[i].index]) && shouldShowDataPoint ) { altitudeDataY.get(i).add(data.data[chart.xTypes[i].index].getDecimalValue()); + dataPointsAdded++; } else { // Hidden data altitudeDataY.get(i).add(null); From 782caf83801fdf4c3c486a1dce16dbbc85221352 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 4 Nov 2020 02:43:48 -0500 Subject: [PATCH 4/4] Switched latest button to use toggle --- src/uorocketry/basestation/Main.java | 26 +++++++++++++++----------- src/uorocketry/basestation/Window.java | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/uorocketry/basestation/Main.java b/src/uorocketry/basestation/Main.java index 27eda67..6071903 100644 --- a/src/uorocketry/basestation/Main.java +++ b/src/uorocketry/basestation/Main.java @@ -397,6 +397,11 @@ private void updateUIInternal() { // Set max value of the sliders window.maxSliders.get(i).setMaximum(allData.get(i).size() - 1); window.minSliders.get(i).setMaximum(allData.get(i).size() - 1); + + // Move position to end + if (latest) { + window.maxSliders.get(i).setValue(allData.get(i).size() - 1); + } } DataHandler currentDataHandler = allData.get(i).get(currentDataIndexes.get(i)); @@ -663,7 +668,7 @@ public void stateChanged(ChangeEvent e) { if (e.getSource() instanceof JSlider && window.maxSliders.contains(e.getSource())) { JSlider maxSlider = (JSlider) e.getSource(); int tableIndex = window.maxSliders.indexOf(maxSlider); - + currentDataIndexes.set(tableIndex, maxSlider.getValue()); // Check if min is too high @@ -672,8 +677,6 @@ public void stateChanged(ChangeEvent e) { window.minSliders.get(tableIndex).setValue(minDataIndexes.get(tableIndex)); } - // Update the latest value - latest = maxSlider.getValue() == maxSlider.getMaximum() - 1; updateUI(); } else if (e.getSource() instanceof JSlider && window.minSliders.contains(e.getSource())) { @@ -717,11 +720,6 @@ public void serialEvent(SerialPortEvent e) { updateUI(); - // Move position to end - if (latest) { - window.maxSliders.get(tableIndex).setValue(allData.get(0).size() - 1); - } - // Add this message to the log file logFileStringBuilder.append(delimitedMessage); @@ -763,10 +761,16 @@ public void actionPerformed(ActionEvent e) { } } else if (e.getSource() == window.latestButton) { - latest = true; + latest = !latest; - for (int i = 0; i < window.maxSliders.size(); i++) { latest = true; - window.maxSliders.get(i).setValue(allData.get(0).size() - 1); + if (latest) { + window.latestButton.setText("Detach From Latest"); + + for (int i = 0; i < window.maxSliders.size(); i++) { + window.maxSliders.get(i).setValue(allData.get(0).size() - 1); + } + } else { + window.latestButton.setText("Latest"); } } else if (e.getSource() == window.addChartButton) { addChart(); diff --git a/src/uorocketry/basestation/Window.java b/src/uorocketry/basestation/Window.java index 6cc89d7..e924572 100644 --- a/src/uorocketry/basestation/Window.java +++ b/src/uorocketry/basestation/Window.java @@ -189,7 +189,7 @@ public Window(Main main) { pauseButton = new JButton("Pause"); eastSliderButtons.add(pauseButton); - latestButton = new JButton("Latest"); + latestButton = new JButton("Detach From Latest"); eastSliderButtons.add(latestButton); sidePanel = new JPanel();