1 package net.sf.openrocket.gui.main;
4 import java.awt.Dialog;
5 import java.awt.Dimension;
6 import java.awt.Window;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.awt.event.WindowAdapter;
10 import java.awt.event.WindowEvent;
11 import java.util.Iterator;
12 import java.util.List;
13 import java.util.concurrent.ExecutorService;
14 import java.util.concurrent.Executors;
16 import javax.swing.JButton;
17 import javax.swing.JDialog;
18 import javax.swing.JLabel;
19 import javax.swing.JOptionPane;
20 import javax.swing.JPanel;
21 import javax.swing.JProgressBar;
23 import net.miginfocom.swing.MigLayout;
24 import net.sf.openrocket.document.Simulation;
25 import net.sf.openrocket.gui.dialogs.DetailDialog;
26 import net.sf.openrocket.gui.util.GUIUtil;
27 import net.sf.openrocket.l10n.Translator;
28 import net.sf.openrocket.logging.LogHelper;
29 import net.sf.openrocket.rocketcomponent.Configuration;
30 import net.sf.openrocket.rocketcomponent.MotorMount;
31 import net.sf.openrocket.rocketcomponent.MotorMount.IgnitionEvent;
32 import net.sf.openrocket.simulation.FlightEvent;
33 import net.sf.openrocket.simulation.SimulationStatus;
34 import net.sf.openrocket.simulation.exception.SimulationCancelledException;
35 import net.sf.openrocket.simulation.exception.SimulationException;
36 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
37 import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
38 import net.sf.openrocket.simulation.listeners.SimulationListener;
39 import net.sf.openrocket.startup.Application;
40 import net.sf.openrocket.unit.Unit;
41 import net.sf.openrocket.unit.UnitGroup;
42 import net.sf.openrocket.util.MathUtil;
43 import net.sf.openrocket.util.Prefs;
46 public class SimulationRunDialog extends JDialog {
47 private static final LogHelper log = Application.getLogger();
48 private static final Translator trans = Application.getTranslator();
51 /** Update the dialog status every this many ms */
52 private static final long UPDATE_MS = 200;
54 /** Flight progress at motor burnout */
55 private static final double BURNOUT_PROGRESS = 0.4;
57 /** Flight progress at apogee */
58 private static final double APOGEE_PROGRESS = 0.7;
62 * The executor service is not static since we want concurrent simulation
63 * dialogs to run in parallel, ie. they both have their own executor service.
65 private final ExecutorService executor = Executors.newFixedThreadPool(
66 Prefs.getMaxThreadCount());
69 private final JLabel simLabel, timeLabel, altLabel, velLabel;
70 private final JProgressBar progressBar;
74 * NOTE: Care must be used when accessing the simulation parameters, since they
75 * are being run in another thread. Mutexes are used to avoid concurrent usage, which
76 * will result in an exception being thrown!
78 private final Simulation[] simulations;
79 private final String[] simulationNames;
80 private final SimulationWorker[] simulationWorkers;
81 private final SimulationStatus[] simulationStatuses;
82 private final double[] simulationMaxAltitude;
83 private final double[] simulationMaxVelocity;
84 private final boolean[] simulationDone;
86 public SimulationRunDialog(Window window, Simulation... simulations) {
87 //// Running simulations...
88 super(window, trans.get("SimuRunDlg.title.RunSim"), Dialog.ModalityType.DOCUMENT_MODAL);
90 if (simulations.length == 0) {
91 throw new IllegalArgumentException("Called with no simulations to run");
94 this.simulations = simulations;
97 // Randomize the simulation random seeds
98 for (Simulation sim : simulations) {
99 sim.getOptions().randomizeSeed();
102 // Initialize the simulations
103 int n = simulations.length;
104 simulationNames = new String[n];
105 simulationWorkers = new SimulationWorker[n];
106 simulationStatuses = new SimulationStatus[n];
107 simulationMaxAltitude = new double[n];
108 simulationMaxVelocity = new double[n];
109 simulationDone = new boolean[n];
111 for (int i = 0; i < n; i++) {
112 simulationNames[i] = simulations[i].getName();
113 simulationWorkers[i] = new InteractiveSimulationWorker(simulations[i], i);
114 executor.execute(simulationWorkers[i]);
118 JPanel panel = new JPanel(new MigLayout("fill", "[][grow]"));
121 simLabel = new JLabel(trans.get("SimuRunDlg.lbl.Running"));
122 panel.add(simLabel, "spanx, wrap para");
123 //// Simulation time:
124 panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Simutime") + " "), "gapright para");
125 timeLabel = new JLabel("");
126 panel.add(timeLabel, "growx, wmin 200lp, wrap rel");
129 panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Altitude") + " "));
130 altLabel = new JLabel("");
131 panel.add(altLabel, "growx, wrap rel");
134 panel.add(new JLabel(trans.get("SimuRunDlg.lbl.Velocity") + " "));
135 velLabel = new JLabel("");
136 panel.add(velLabel, "growx, wrap para");
138 progressBar = new JProgressBar();
139 panel.add(progressBar, "spanx, growx, wrap para");
143 JButton cancel = new JButton(trans.get("dlg.but.cancel"));
144 cancel.addActionListener(new ActionListener() {
146 public void actionPerformed(ActionEvent e) {
150 panel.add(cancel, "spanx, tag cancel");
153 // Cancel simulations when user closes the window
154 this.addWindowListener(new WindowAdapter() {
156 public void windowClosing(WindowEvent e) {
163 this.setMinimumSize(new Dimension(300, 0));
164 this.setLocationByPlatform(true);
168 GUIUtil.setDisposableDialogOptions(this, null);
175 * Cancel the currently running simulations. This is equivalent to clicking
176 * the Cancel button on the dialog.
178 public void cancelSimulations() {
179 executor.shutdownNow();
180 for (SimulationWorker w : simulationWorkers) {
187 * Static helper method to run simulations.
189 * @param parent the parent Window of the dialog to use.
190 * @param simulations the simulations to run.
192 public static void runSimulations(Window parent, Simulation... simulations) {
193 new SimulationRunDialog(parent, simulations).setVisible(true);
199 private void updateProgress() {
201 for (index = 0; index < simulations.length; index++) {
202 if (!simulationDone[index])
206 if (index >= simulations.length) {
207 // Everything is done, close the dialog
208 log.debug("Everything done.");
213 // Update the progress bar status
215 for (SimulationWorker s : simulationWorkers) {
216 progress += s.getProgress();
218 progress /= simulationWorkers.length;
219 progressBar.setValue(progress);
220 log.debug("Progressbar value " + progress);
222 // Update the simulation fields
223 simLabel.setText("Running " + simulationNames[index]);
224 if (simulationStatuses[index] == null) {
225 log.debug("No simulation status data available, setting empty labels");
226 timeLabel.setText("");
227 altLabel.setText("");
228 velLabel.setText("");
232 Unit u = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
233 timeLabel.setText(u.toStringUnit(simulationStatuses[index].getSimulationTime()));
235 u = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
236 altLabel.setText(u.toStringUnit(simulationStatuses[index].getRocketPosition().z) + " (max. " +
237 u.toStringUnit(simulationMaxAltitude[index]) + ")");
239 u = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
240 velLabel.setText(u.toStringUnit(simulationStatuses[index].getRocketVelocity().z) + " (max. " +
241 u.toStringUnit(simulationMaxVelocity[index]) + ")");
247 * A SwingWorker that performs a flight simulation. It periodically updates the
248 * simulation statuses of the parent class and calls updateProgress().
249 * The progress of the simulation is stored in the progress property of the
252 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
254 private class InteractiveSimulationWorker extends SimulationWorker {
256 private final int index;
257 private final double burnoutTimeEstimate;
258 private volatile double burnoutVelocity;
259 private volatile double apogeeAltitude;
262 * -2 = time from 0 ... burnoutTimeEstimate
263 * -1 = velocity from v(burnoutTimeEstimate) ... 0
264 * 0 ... n = stages from alt(max) ... 0
266 private volatile int simulationStage = -2;
268 private int progress = 0;
271 public InteractiveSimulationWorker(Simulation sim, int index) {
275 // Calculate estimate of motor burn time
276 double launchBurn = 0;
277 double otherBurn = 0;
278 Configuration config = simulation.getConfiguration();
279 String id = simulation.getOptions().getMotorConfigurationID();
280 Iterator<MotorMount> iterator = config.motorIterator();
281 while (iterator.hasNext()) {
282 MotorMount m = iterator.next();
283 if (m.getIgnitionEvent() == IgnitionEvent.LAUNCH)
284 launchBurn = MathUtil.max(launchBurn, m.getMotor(id).getBurnTimeEstimate());
286 otherBurn = otherBurn + m.getMotor(id).getBurnTimeEstimate();
288 burnoutTimeEstimate = Math.max(launchBurn + otherBurn, 0.1);
294 * Return the extra listeners to use, a progress listener and cancel listener.
297 protected SimulationListener[] getExtraListeners() {
298 return new SimulationListener[] { new SimulationProgressListener() };
303 * Processes simulation statuses published by the simulation listener.
304 * The statuses of the parent class and the progress property are updated.
307 protected void process(List<SimulationStatus> chunks) {
309 // Update max. altitude and velocity
310 for (SimulationStatus s : chunks) {
311 simulationMaxAltitude[index] = Math.max(simulationMaxAltitude[index],
312 s.getRocketPosition().z);
313 simulationMaxVelocity[index] = Math.max(simulationMaxVelocity[index],
314 s.getRocketVelocity().length());
317 // Calculate the progress
318 SimulationStatus status = chunks.get(chunks.size() - 1);
319 simulationStatuses[index] = status;
321 // 1. time = 0 ... burnoutTimeEstimate
322 if (simulationStage == -2 && status.getSimulationTime() < burnoutTimeEstimate) {
323 log.debug("Method 1: t=" + status.getSimulationTime() + " est=" + burnoutTimeEstimate);
324 setSimulationProgress(MathUtil.map(status.getSimulationTime(), 0, burnoutTimeEstimate,
325 0.0, BURNOUT_PROGRESS));
330 if (simulationStage == -2) {
332 burnoutVelocity = MathUtil.max(status.getRocketVelocity().z, 0.1);
333 log.debug("CHANGING to Method 2, vel=" + burnoutVelocity);
336 // 2. z-velocity from burnout velocity to zero
337 if (simulationStage == -1 && status.getRocketVelocity().z >= 0) {
338 log.debug("Method 2: vel=" + status.getRocketVelocity().z + " burnout=" + burnoutVelocity);
339 setSimulationProgress(MathUtil.map(status.getRocketVelocity().z, burnoutVelocity, 0,
340 BURNOUT_PROGRESS, APOGEE_PROGRESS));
345 if (simulationStage == -1 && status.getRocketVelocity().z < 0) {
347 apogeeAltitude = MathUtil.max(status.getRocketPosition().z, 1);
348 log.debug("CHANGING to Method 3, apogee=" + apogeeAltitude);
351 // 3. z-position from apogee to zero
352 // TODO: MEDIUM: several stages
353 log.debug("Method 3: alt=" + status.getRocketPosition().z + " apogee=" + apogeeAltitude);
354 setSimulationProgress(MathUtil.map(status.getRocketPosition().z,
355 apogeeAltitude, 0, APOGEE_PROGRESS, 1.0));
360 * Marks this simulation as done and calls the progress update.
363 protected void simulationDone() {
364 simulationDone[index] = true;
365 log.debug("Simulation done");
366 setSimulationProgress(1.0);
372 * Marks the simulation as done and shows a dialog presenting
373 * the error, unless the simulation was cancelled.
376 protected void simulationInterrupted(Throwable t) {
378 if (t instanceof SimulationCancelledException) {
380 return; // Ignore cancellations
383 // Analyze the exception type
384 if (t instanceof SimulationLaunchException) {
386 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
388 //// Unable to simulate:
389 trans.get("SimuRunDlg.msg.Unabletosim"),
392 null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
394 } else if (t instanceof SimulationException) {
396 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
398 //// A error occurred during the simulation:
399 trans.get("SimuRunDlg.msg.errorOccurred"),
402 null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
406 ExceptionHandler.handleErrorCondition("An exception occurred during the simulation", t);
413 private void setSimulationProgress(double p) {
414 int exact = Math.max(progress, (int) (100 * p + 0.5));
415 progress = MathUtil.clamp(exact, 0, 100);
416 log.debug("Setting progress to " + progress + " (real " + exact + ")");
417 super.setProgress(progress);
422 * A simulation listener that regularly updates the progress property of the
423 * SimulationWorker and publishes the simulation status for the run dialog to process.
425 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
427 private class SimulationProgressListener extends AbstractSimulationListener {
428 private long time = 0;
431 public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
432 switch (event.getType()) {
435 apogeeAltitude = status.getRocketPosition().z;
436 log.debug("APOGEE, setting progress");
437 setSimulationProgress(APOGEE_PROGRESS);
446 log.debug("END, setting progress");
447 setSimulationProgress(1.0);
454 public void postStep(SimulationStatus status) {
455 if (System.currentTimeMillis() >= time + UPDATE_MS) {
456 time = System.currentTimeMillis();