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.io.CharArrayWriter;
12 import java.io.PrintWriter;
13 import java.util.Collection;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.concurrent.ExecutorService;
17 import java.util.concurrent.Executors;
19 import javax.swing.JButton;
20 import javax.swing.JDialog;
21 import javax.swing.JLabel;
22 import javax.swing.JOptionPane;
23 import javax.swing.JPanel;
24 import javax.swing.JProgressBar;
26 import net.miginfocom.swing.MigLayout;
27 import net.sf.openrocket.document.Simulation;
28 import net.sf.openrocket.gui.dialogs.DetailDialog;
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.SimulationListener;
34 import net.sf.openrocket.simulation.SimulationStatus;
35 import net.sf.openrocket.simulation.exception.SimulationCancelledException;
36 import net.sf.openrocket.simulation.exception.SimulationException;
37 import net.sf.openrocket.simulation.exception.SimulationLaunchException;
38 import net.sf.openrocket.simulation.listeners.AbstractSimulationListener;
39 import net.sf.openrocket.unit.Unit;
40 import net.sf.openrocket.unit.UnitGroup;
41 import net.sf.openrocket.util.GUIUtil;
42 import net.sf.openrocket.util.MathUtil;
43 import net.sf.openrocket.util.Prefs;
46 public class SimulationRunDialog extends JDialog {
47 /** Update the dialog status every this many ms */
48 private static final long UPDATE_MS = 200;
50 /** Flight progress at motor burnout */
51 private static final double BURNOUT_PROGRESS = 0.4;
53 /** Flight progress at apogee */
54 private static final double APOGEE_PROGRESS = 0.7;
58 * The executor service is not static since we want concurrent simulation
59 * dialogs to run in parallel, ie. they both have their own executor service.
61 private final ExecutorService executor = Executors.newFixedThreadPool(
62 Prefs.getMaxThreadCount());
65 private final JLabel simLabel, timeLabel, altLabel, velLabel;
66 private final JProgressBar progressBar;
69 private final Simulation[] simulations;
70 private final SimulationWorker[] simulationWorkers;
71 private final SimulationStatus[] simulationStatuses;
72 private final double[] simulationMaxAltitude;
73 private final double[] simulationMaxVelocity;
74 private final boolean[] simulationDone;
76 public SimulationRunDialog(Window window, Simulation ... simulations) {
77 super(window, "Running simulations...", Dialog.ModalityType.DOCUMENT_MODAL);
79 if (simulations.length == 0) {
80 throw new IllegalArgumentException("Called with no simulations to run");
83 this.simulations = simulations;
85 // Initialize the simulations
86 int n = simulations.length;
87 simulationWorkers = new SimulationWorker[n];
88 simulationStatuses = new SimulationStatus[n];
89 simulationMaxAltitude = new double[n];
90 simulationMaxVelocity = new double[n];
91 simulationDone = new boolean[n];
93 for (int i=0; i<n; i++) {
94 simulationWorkers[i] = new InteractiveSimulationWorker(simulations[i], i);
95 executor.execute(simulationWorkers[i]);
99 JPanel panel = new JPanel(new MigLayout("fill", "[][grow]"));
101 simLabel = new JLabel("Running ...");
102 panel.add(simLabel, "spanx, wrap para");
104 panel.add(new JLabel("Simulation time: "), "gapright para");
105 timeLabel = new JLabel("");
106 panel.add(timeLabel, "growx, wrap rel");
108 panel.add(new JLabel("Altitude: "));
109 altLabel = new JLabel("");
110 panel.add(altLabel, "growx, wrap rel");
112 panel.add(new JLabel("Velocity: "));
113 velLabel = new JLabel("");
114 panel.add(velLabel, "growx, wrap para");
116 progressBar = new JProgressBar();
117 panel.add(progressBar, "spanx, growx, wrap para");
121 JButton cancel = new JButton("Cancel");
122 cancel.addActionListener(new ActionListener() {
124 public void actionPerformed(ActionEvent e) {
128 panel.add(cancel, "spanx, tag cancel");
131 // Cancel simulations when user closes the window
132 this.addWindowListener(new WindowAdapter() {
134 public void windowClosing(WindowEvent e) {
141 this.setMinimumSize(new Dimension(300, 0));
142 this.setLocationByPlatform(true);
145 GUIUtil.installEscapeCloseOperation(this);
152 * Cancel the currently running simulations. This is equivalent to clicking
153 * the Cancel button on the dialog.
155 public void cancelSimulations() {
156 executor.shutdownNow();
157 for (SimulationWorker w: simulationWorkers) {
164 * Static helper method to run simulations.
166 * @param parent the parent Window of the dialog to use.
167 * @param simulations the simulations to run.
169 public static void runSimulations(Window parent, Simulation ... simulations) {
170 new SimulationRunDialog(parent, simulations).setVisible(true);
176 private void updateProgress() {
177 System.out.println("updateProgress() called");
179 for (index=0; index < simulations.length; index++) {
180 if (!simulationDone[index])
184 if (index >= simulations.length) {
185 // Everything is done, close the dialog
186 System.out.println("Everything done.");
191 // Update the progress bar status
193 for (SimulationWorker s: simulationWorkers) {
194 progress += s.getProgress();
196 progress /= simulationWorkers.length;
197 progressBar.setValue(progress);
198 System.out.println("Progressbar value "+progress);
200 // Update the simulation fields
201 simLabel.setText("Running " + simulations[index].getName());
202 if (simulationStatuses[index] == null) {
203 timeLabel.setText("");
204 altLabel.setText("");
205 velLabel.setText("");
206 System.out.println("Empty labels, how sad.");
210 Unit u = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
211 timeLabel.setText(u.toStringUnit(simulationStatuses[index].time));
213 u = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
214 altLabel.setText(u.toStringUnit(simulationStatuses[index].position.z) + " (max. " +
215 u.toStringUnit(simulationMaxAltitude[index]) + ")");
217 u = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
218 velLabel.setText(u.toStringUnit(simulationStatuses[index].velocity.z) + " (max. " +
219 u.toStringUnit(simulationMaxVelocity[index]) + ")");
220 System.out.println("Set interesting labels.");
226 * A SwingWorker that performs a flight simulation. It periodically updates the
227 * simulation statuses of the parent class and calls updateProgress().
228 * The progress of the simulation is stored in the progress property of the
231 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
233 private class InteractiveSimulationWorker extends SimulationWorker {
235 private final int index;
236 private final double burnoutTimeEstimate;
237 private volatile double burnoutVelocity;
238 private volatile double apogeeAltitude;
241 * -2 = time from 0 ... burnoutTimeEstimate
242 * -1 = velocity from v(burnoutTimeEstimate) ... 0
243 * 0 ... n = stages from alt(max) ... 0
245 private volatile int simulationStage = -2;
247 private int progress = 0;
250 public InteractiveSimulationWorker(Simulation sim, int index) {
254 // Calculate estimate of motor burn time
255 double launchBurn = 0;
256 double otherBurn = 0;
257 Configuration config = simulation.getConfiguration();
258 String id = simulation.getConditions().getMotorConfigurationID();
259 Iterator<MotorMount> iterator = config.motorIterator();
260 while (iterator.hasNext()) {
261 MotorMount m = iterator.next();
262 if (m.getIgnitionEvent() == IgnitionEvent.LAUNCH)
263 launchBurn = MathUtil.max(launchBurn, m.getMotor(id).getTotalTime());
265 otherBurn = otherBurn + m.getMotor(id).getTotalTime();
267 burnoutTimeEstimate = Math.max(launchBurn + otherBurn, 0.1);
273 * Return the extra listeners to use, a progress listener and cancel listener.
276 protected SimulationListener[] getExtraListeners() {
277 return new SimulationListener[] {
278 new SimulationProgressListener()
284 * Processes simulation statuses published by the simulation listener.
285 * The statuses of the parent class and the progress property are updated.
288 protected void process(List<SimulationStatus> chunks) {
290 // Update max. altitude and velocity
291 for (SimulationStatus s: chunks) {
292 simulationMaxAltitude[index] = Math.max(simulationMaxAltitude[index],
294 simulationMaxVelocity[index] = Math.max(simulationMaxVelocity[index],
295 s.velocity.length());
298 // Calculate the progress
299 SimulationStatus status = chunks.get(chunks.size()-1);
300 simulationStatuses[index] = status;
302 // 1. time = 0 ... burnoutTimeEstimate
303 if (simulationStage == -2 && status.time < burnoutTimeEstimate) {
304 System.out.println("Method 1: t="+status.time + " est="+burnoutTimeEstimate);
305 setSimulationProgress(MathUtil.map(status.time, 0, burnoutTimeEstimate,
306 0.0, BURNOUT_PROGRESS));
311 if (simulationStage == -2) {
313 burnoutVelocity = MathUtil.max(status.velocity.z, 0.1);
314 System.out.println("CHANGING to Method 2, vel="+burnoutVelocity);
317 // 2. z-velocity from burnout velocity to zero
318 if (simulationStage == -1 && status.velocity.z >= 0) {
319 System.out.println("Method 2: vel="+status.velocity.z + " burnout=" +
321 setSimulationProgress(MathUtil.map(status.velocity.z, burnoutVelocity, 0,
322 BURNOUT_PROGRESS, APOGEE_PROGRESS));
327 if (simulationStage == -1 && status.velocity.z < 0) {
329 apogeeAltitude = status.position.z;
332 // 3. z-position from apogee to zero
333 // TODO: MEDIUM: several stages
334 System.out.println("Method 3: alt="+status.position.z +" apogee="+apogeeAltitude);
335 setSimulationProgress(MathUtil.map(status.position.z,
336 apogeeAltitude, 0, APOGEE_PROGRESS, 1.0));
341 * Marks this simulation as done and calls the progress update.
344 protected void simulationDone() {
345 simulationDone[index] = true;
346 System.out.println("DONE, setting progress");
347 setSimulationProgress(1.0);
353 * Marks the simulation as done and shows a dialog presenting
354 * the error, unless the simulation was cancelled.
357 protected void simulationInterrupted(Throwable t) {
359 if (t instanceof SimulationCancelledException) {
361 return; // Ignore cancellations
364 // Retrieve the stack trace in a textual form
365 CharArrayWriter arrayWriter = new CharArrayWriter();
366 arrayWriter.append(t.toString() + "\n" + "\n");
367 t.printStackTrace(new PrintWriter(arrayWriter));
368 String stackTrace = arrayWriter.toString();
370 // Analyze the exception type
371 if (t instanceof SimulationLaunchException) {
373 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
375 "Unable to simulate:",
378 null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
380 } else if (t instanceof SimulationException) {
382 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
384 "A error occurred during the simulation:",
387 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
389 } else if (t instanceof Exception) {
391 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
393 "An exception occurred during the simulation:",
395 simulation.getSimulationListeners().isEmpty() ?
396 "Please report this as a bug along with the details below." : ""
398 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
400 } else if (t instanceof AssertionError) {
402 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
404 "A computation error occurred during the simulation.",
405 "Please report this as a bug along with the details below."
407 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
412 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
414 "An unknown error was encountered during the simulation.",
415 "The program may be unstable, you should save all your designs " +
416 "and restart OpenRocket now!"
418 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
426 private void setSimulationProgress(double p) {
427 progress = Math.max(progress, (int)(100*p+0.5));
428 progress = MathUtil.clamp(progress, 0, 100);
429 System.out.println("Setting progress to "+progress+ " (real " +
430 ((int)(100*p+0.5)) + ")");
431 super.setProgress(progress);
436 * A simulation listener that regularly updates the progress property of the
437 * SimulationWorker and publishes the simulation status for the run dialog to process.
439 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
441 private class SimulationProgressListener extends AbstractSimulationListener {
442 private long time = 0;
445 public Collection<FlightEvent> handleEvent(FlightEvent event,
446 SimulationStatus status) {
448 switch (event.getType()) {
451 apogeeAltitude = status.position.z;
452 System.out.println("APOGEE, setting progress");
453 setSimulationProgress(APOGEE_PROGRESS);
462 System.out.println("END, setting progress");
463 setSimulationProgress(1.0);
470 public Collection<FlightEvent> stepTaken(SimulationStatus status) {
471 if (System.currentTimeMillis() >= time + UPDATE_MS) {
472 time = System.currentTimeMillis();