release 0.9.6
[debian/openrocket] / src / net / sf / openrocket / gui / main / SimulationRunDialog.java
1 package net.sf.openrocket.gui.main;
2
3
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;
18
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;
25
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;
44
45
46 public class SimulationRunDialog extends JDialog {
47         /** Update the dialog status every this many ms */
48         private static final long UPDATE_MS = 200;
49         
50         /** Flight progress at motor burnout */
51         private static final double BURNOUT_PROGRESS = 0.4;
52         
53         /** Flight progress at apogee */
54         private static final double APOGEE_PROGRESS = 0.7;
55         
56         
57         /*
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.
60          */
61         private final ExecutorService executor = Executors.newFixedThreadPool(
62                         Prefs.getMaxThreadCount());
63         
64         
65         private final JLabel simLabel, timeLabel, altLabel, velLabel;
66         private final JProgressBar progressBar;
67         
68         
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;
75         
76         public SimulationRunDialog(Window window, Simulation ... simulations) {
77                 super(window, "Running simulations...", Dialog.ModalityType.DOCUMENT_MODAL);
78                 
79                 if (simulations.length == 0) {
80                         throw new IllegalArgumentException("Called with no simulations to run");
81                 }
82                 
83                 this.simulations = simulations;
84         
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];
92                 
93                 for (int i=0; i<n; i++) {
94                         simulationWorkers[i] = new InteractiveSimulationWorker(simulations[i], i);
95                         executor.execute(simulationWorkers[i]);
96                 }
97                 
98                 // Build the dialog
99                 JPanel panel = new JPanel(new MigLayout("fill", "[][grow]"));
100                 
101                 simLabel = new JLabel("Running ...");
102                 panel.add(simLabel, "spanx, wrap para");
103                 
104                 panel.add(new JLabel("Simulation time: "), "gapright para");
105                 timeLabel = new JLabel("");
106                 panel.add(timeLabel, "growx, wrap rel");
107                 
108                 panel.add(new JLabel("Altitude: "));
109                 altLabel = new JLabel("");
110                 panel.add(altLabel, "growx, wrap rel");
111                 
112                 panel.add(new JLabel("Velocity: "));
113                 velLabel = new JLabel("");
114                 panel.add(velLabel, "growx, wrap para");
115                 
116                 progressBar = new JProgressBar();
117                 panel.add(progressBar, "spanx, growx, wrap para");
118                 
119                 
120                 // Add cancel button
121                 JButton cancel = new JButton("Cancel");
122                 cancel.addActionListener(new ActionListener() {
123                         @Override
124                         public void actionPerformed(ActionEvent e) {
125                                 cancelSimulations();
126                         }
127                 });
128                 panel.add(cancel, "spanx, tag cancel");
129                 
130                 
131                 // Cancel simulations when user closes the window
132                 this.addWindowListener(new WindowAdapter() {
133                         @Override
134                         public void windowClosing(WindowEvent e) {
135                                 cancelSimulations();
136                         }
137                 });
138                 
139                 
140                 this.add(panel);
141                 this.setMinimumSize(new Dimension(300, 0));
142                 this.setLocationByPlatform(true);
143                 this.validate();
144                 this.pack();
145                 
146                 GUIUtil.setDisposableDialogOptions(this, null);
147
148                 updateProgress();
149         }
150         
151         
152         /**
153          * Cancel the currently running simulations.  This is equivalent to clicking
154          * the Cancel button on the dialog.
155          */
156         public void cancelSimulations() {
157                 executor.shutdownNow();
158                 for (SimulationWorker w: simulationWorkers) {
159                         w.cancel(true);
160                 }
161         }
162         
163         
164         /**
165          * Static helper method to run simulations.
166          * 
167          * @param parent                the parent Window of the dialog to use.
168          * @param simulations   the simulations to run.
169          */
170         public static void runSimulations(Window parent, Simulation ... simulations) {
171                 new SimulationRunDialog(parent, simulations).setVisible(true);
172         }
173         
174         
175         
176         
177         private void updateProgress() {
178                 System.out.println("updateProgress() called");
179                 int index;
180                 for (index=0; index < simulations.length; index++) {
181                         if (!simulationDone[index])
182                                 break;
183                 }
184                 
185                 if (index >= simulations.length) {
186                         // Everything is done, close the dialog
187                         System.out.println("Everything done.");
188                         this.dispose();
189                         return;
190                 }
191
192                 // Update the progress bar status
193                 int progress = 0;
194                 for (SimulationWorker s: simulationWorkers) {
195                         progress += s.getProgress();
196                 }
197                 progress /= simulationWorkers.length;
198                 progressBar.setValue(progress);
199                 System.out.println("Progressbar value "+progress);
200                 
201                 // Update the simulation fields
202                 simLabel.setText("Running " + simulations[index].getName());
203                 if (simulationStatuses[index] == null) {
204                         timeLabel.setText("");
205                         altLabel.setText("");
206                         velLabel.setText("");
207                         System.out.println("Empty labels, how sad.");
208                         return;
209                 }
210                 
211                 Unit u = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
212                 timeLabel.setText(u.toStringUnit(simulationStatuses[index].time));
213                 
214                 u = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
215                 altLabel.setText(u.toStringUnit(simulationStatuses[index].position.z) + " (max. " +
216                                 u.toStringUnit(simulationMaxAltitude[index]) + ")");
217                 
218                 u = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
219                 velLabel.setText(u.toStringUnit(simulationStatuses[index].velocity.z) + " (max. " +
220                                 u.toStringUnit(simulationMaxVelocity[index]) + ")");
221                 System.out.println("Set interesting labels.");
222         }
223
224         
225         
226         /**
227          * A SwingWorker that performs a flight simulation.  It periodically updates the
228          * simulation statuses of the parent class and calls updateProgress().
229          * The progress of the simulation is stored in the progress property of the
230          * SwingWorker.
231          * 
232          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
233          */
234         private class InteractiveSimulationWorker extends SimulationWorker {
235
236                 private final int index;
237                 private final double burnoutTimeEstimate;
238                 private volatile double burnoutVelocity;
239                 private volatile double apogeeAltitude;
240                 
241                 /*
242                  * -2 = time from 0 ... burnoutTimeEstimate
243                  * -1 = velocity from v(burnoutTimeEstimate) ... 0
244                  *  0 ... n = stages from alt(max) ... 0
245                  */
246                 private volatile int simulationStage = -2;
247                 
248                 private int progress = 0;
249                 
250                 
251                 public InteractiveSimulationWorker(Simulation sim, int index) {
252                         super(sim);
253                         this.index = index;
254
255                         // Calculate estimate of motor burn time
256                         double launchBurn = 0;
257                         double otherBurn = 0;
258                         Configuration config = simulation.getConfiguration();
259                         String id = simulation.getConditions().getMotorConfigurationID();
260                         Iterator<MotorMount> iterator = config.motorIterator();
261                         while (iterator.hasNext()) {
262                                 MotorMount m = iterator.next();
263                                 if (m.getIgnitionEvent() == IgnitionEvent.LAUNCH)
264                                         launchBurn = MathUtil.max(launchBurn, m.getMotor(id).getTotalTime());
265                                 else
266                                         otherBurn = otherBurn + m.getMotor(id).getTotalTime();
267                         }
268                         burnoutTimeEstimate = Math.max(launchBurn + otherBurn, 0.1);
269                         
270                 }
271
272
273                 /**
274                  * Return the extra listeners to use, a progress listener and cancel listener.
275                  */
276                 @Override
277                 protected SimulationListener[] getExtraListeners() {
278                         return new SimulationListener[] {
279                                         new SimulationProgressListener()
280                         };
281                 }
282
283                 
284                 /**
285                  * Processes simulation statuses published by the simulation listener.
286                  * The statuses of the parent class and the progress property are updated.
287                  */
288                 @Override
289                 protected void process(List<SimulationStatus> chunks) {
290                         
291                         // Update max. altitude and velocity
292                         for (SimulationStatus s: chunks) {
293                                 simulationMaxAltitude[index] = Math.max(simulationMaxAltitude[index], 
294                                                 s.position.z);
295                                 simulationMaxVelocity[index] = Math.max(simulationMaxVelocity[index], 
296                                                 s.velocity.length());
297                         }
298
299                         // Calculate the progress
300                         SimulationStatus status = chunks.get(chunks.size()-1);
301                         simulationStatuses[index] = status;
302
303                         // 1. time = 0 ... burnoutTimeEstimate
304                         if (simulationStage == -2 && status.time < burnoutTimeEstimate) {
305                                 System.out.println("Method 1:  t="+status.time + "  est="+burnoutTimeEstimate);
306                                 setSimulationProgress(MathUtil.map(status.time, 0, burnoutTimeEstimate, 
307                                                 0.0, BURNOUT_PROGRESS));
308                                 updateProgress();
309                                 return;
310                         }
311                         
312                         if (simulationStage == -2) {
313                                 simulationStage++;
314                                 burnoutVelocity = MathUtil.max(status.velocity.z, 0.1);
315                                 System.out.println("CHANGING to Method 2, vel="+burnoutVelocity);
316                         }
317                         
318                         // 2. z-velocity from burnout velocity to zero
319                         if (simulationStage == -1 && status.velocity.z >= 0) {
320                                 System.out.println("Method 2:  vel="+status.velocity.z + " burnout=" +
321                                                 burnoutVelocity);
322                                 setSimulationProgress(MathUtil.map(status.velocity.z, burnoutVelocity, 0,
323                                                 BURNOUT_PROGRESS, APOGEE_PROGRESS));
324                                 updateProgress();
325                                 return;
326                         }
327                         
328                         if (simulationStage == -1 && status.velocity.z < 0) {
329                                 simulationStage++;
330                                 apogeeAltitude = MathUtil.max(status.position.z, 1);
331                         }
332                         
333                         // 3. z-position from apogee to zero
334                         // TODO: MEDIUM: several stages
335                         System.out.println("Method 3:  alt="+status.position.z +"  apogee="+apogeeAltitude);
336                         setSimulationProgress(MathUtil.map(status.position.z, 
337                                         apogeeAltitude, 0, APOGEE_PROGRESS, 1.0));
338                         updateProgress();
339                 }
340                 
341                 /**
342                  * Marks this simulation as done and calls the progress update.
343                  */
344                 @Override
345                 protected void simulationDone() {
346                         simulationDone[index] = true;
347                         System.out.println("DONE, setting progress");
348                         setSimulationProgress(1.0);
349                         updateProgress();
350                 }
351                 
352                 
353                 /**
354                  * Marks the simulation as done and shows a dialog presenting
355                  * the error, unless the simulation was cancelled.
356                  */
357                 @Override
358                 protected void simulationInterrupted(Throwable t) {
359                         
360                         if (t instanceof SimulationCancelledException) {
361                                 simulationDone();
362                                 return;  // Ignore cancellations
363                         }
364                         
365                         // Retrieve the stack trace in a textual form
366                         CharArrayWriter arrayWriter = new CharArrayWriter();
367                         arrayWriter.append(t.toString() + "\n" + "\n");
368                         t.printStackTrace(new PrintWriter(arrayWriter));
369                         String stackTrace = arrayWriter.toString();
370                         
371                         // Analyze the exception type
372                         if (t instanceof SimulationLaunchException) {
373                                 
374                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
375                                                 new Object[] {
376                                                 "Unable to simulate:",
377                                                 t.getMessage()
378                                                 },
379                                                 null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
380                                 
381                         } else if (t instanceof SimulationException) {
382                                 
383                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
384                                                 new Object[] {
385                                                 "A error occurred during the simulation:",
386                                                 t.getMessage()
387                                                 }, 
388                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
389                                 
390                         } else if (t instanceof Exception) {
391                                 
392                                 // TODO: MEDIUM: Check the exception handling here...
393                                 t.printStackTrace();
394                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
395                                                 new Object[] {
396                                                 "An exception occurred during the simulation:",
397                                                 t.getMessage(),
398                                                 simulation.getSimulationListeners().isEmpty() ? 
399                                                 "Please report this as a bug along with the details below." : ""
400                                                 }, 
401                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
402                                 
403                         } else if (t instanceof AssertionError) {
404                                 
405                                 t.printStackTrace();
406                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
407                                                 new Object[] {
408                                                         "A computation error occurred during the simulation.",
409                                                         "Please report this as a bug along with the details below."
410                                                 }, 
411                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
412                                 
413                         } else {
414                                 
415                                 // Probably an Error
416                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
417                                                 new Object[] {
418                                                         "An unknown error was encountered during the simulation.",
419                                                         "The program may be unstable, you should save all your designs " +
420                                                         "and restart OpenRocket now!"
421                                                 }, 
422                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
423                                 
424                         }
425                         simulationDone();
426                 }
427                 
428                 
429
430                 private void setSimulationProgress(double p) {
431                         progress = Math.max(progress, (int)(100*p+0.5));
432                         progress = MathUtil.clamp(progress, 0, 100);
433                         System.out.println("Setting progress to "+progress+ " (real " + 
434                                         ((int)(100*p+0.5)) + ")");
435                         super.setProgress(progress);
436                 }
437
438
439                 /**
440                  * A simulation listener that regularly updates the progress property of the 
441                  * SimulationWorker and publishes the simulation status for the run dialog to process.
442                  * 
443                  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
444                  */
445                 private class SimulationProgressListener extends AbstractSimulationListener {
446                         private long time = 0;
447
448                         @Override
449                         public Collection<FlightEvent> handleEvent(FlightEvent event,
450                                         SimulationStatus status) {
451                                 
452                                 switch (event.getType()) {
453                                 case APOGEE:
454                                         simulationStage = 0;
455                                         apogeeAltitude = status.position.z;
456                                         System.out.println("APOGEE, setting progress");
457                                         setSimulationProgress(APOGEE_PROGRESS);
458                                         publish(status);
459                                         break;
460                                         
461                                 case LAUNCH:
462                                         publish(status);
463                                         break;
464                                         
465                                 case SIMULATION_END:
466                                         System.out.println("END, setting progress");
467                                         setSimulationProgress(1.0);
468                                         break;
469                                 }
470                                 return null;
471                         }
472
473                         @Override
474                         public Collection<FlightEvent> stepTaken(SimulationStatus status) {
475                                 if (System.currentTimeMillis() >= time + UPDATE_MS) {
476                                         time = System.currentTimeMillis();
477                                         publish(status);
478                                 }
479                                 return null;
480                         }
481                 }
482         }
483 }