Initial commit
[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.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                 GUIUtil.installEscapeCloseOperation(this);
146
147                 updateProgress();
148         }
149         
150         
151         /**
152          * Cancel the currently running simulations.  This is equivalent to clicking
153          * the Cancel button on the dialog.
154          */
155         public void cancelSimulations() {
156                 executor.shutdownNow();
157                 for (SimulationWorker w: simulationWorkers) {
158                         w.cancel(true);
159                 }
160         }
161         
162         
163         /**
164          * Static helper method to run simulations.
165          * 
166          * @param parent                the parent Window of the dialog to use.
167          * @param simulations   the simulations to run.
168          */
169         public static void runSimulations(Window parent, Simulation ... simulations) {
170                 new SimulationRunDialog(parent, simulations).setVisible(true);
171         }
172         
173         
174         
175         
176         private void updateProgress() {
177                 System.out.println("updateProgress() called");
178                 int index;
179                 for (index=0; index < simulations.length; index++) {
180                         if (!simulationDone[index])
181                                 break;
182                 }
183                 
184                 if (index >= simulations.length) {
185                         // Everything is done, close the dialog
186                         System.out.println("Everything done.");
187                         this.dispose();
188                         return;
189                 }
190
191                 // Update the progress bar status
192                 int progress = 0;
193                 for (SimulationWorker s: simulationWorkers) {
194                         progress += s.getProgress();
195                 }
196                 progress /= simulationWorkers.length;
197                 progressBar.setValue(progress);
198                 System.out.println("Progressbar value "+progress);
199                 
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.");
207                         return;
208                 }
209                 
210                 Unit u = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
211                 timeLabel.setText(u.toStringUnit(simulationStatuses[index].time));
212                 
213                 u = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
214                 altLabel.setText(u.toStringUnit(simulationStatuses[index].position.z) + " (max. " +
215                                 u.toStringUnit(simulationMaxAltitude[index]) + ")");
216                 
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.");
221         }
222
223         
224         
225         /**
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
229          * SwingWorker.
230          * 
231          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
232          */
233         private class InteractiveSimulationWorker extends SimulationWorker {
234
235                 private final int index;
236                 private final double burnoutTimeEstimate;
237                 private volatile double burnoutVelocity;
238                 private volatile double apogeeAltitude;
239                 
240                 /*
241                  * -2 = time from 0 ... burnoutTimeEstimate
242                  * -1 = velocity from v(burnoutTimeEstimate) ... 0
243                  *  0 ... n = stages from alt(max) ... 0
244                  */
245                 private volatile int simulationStage = -2;
246                 
247                 private int progress = 0;
248                 
249                 
250                 public InteractiveSimulationWorker(Simulation sim, int index) {
251                         super(sim);
252                         this.index = index;
253
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());
264                                 else
265                                         otherBurn = otherBurn + m.getMotor(id).getTotalTime();
266                         }
267                         burnoutTimeEstimate = Math.max(launchBurn + otherBurn, 0.1);
268                         
269                 }
270
271
272                 /**
273                  * Return the extra listeners to use, a progress listener and cancel listener.
274                  */
275                 @Override
276                 protected SimulationListener[] getExtraListeners() {
277                         return new SimulationListener[] {
278                                         new SimulationProgressListener()
279                         };
280                 }
281
282                 
283                 /**
284                  * Processes simulation statuses published by the simulation listener.
285                  * The statuses of the parent class and the progress property are updated.
286                  */
287                 @Override
288                 protected void process(List<SimulationStatus> chunks) {
289                         
290                         // Update max. altitude and velocity
291                         for (SimulationStatus s: chunks) {
292                                 simulationMaxAltitude[index] = Math.max(simulationMaxAltitude[index], 
293                                                 s.position.z);
294                                 simulationMaxVelocity[index] = Math.max(simulationMaxVelocity[index], 
295                                                 s.velocity.length());
296                         }
297
298                         // Calculate the progress
299                         SimulationStatus status = chunks.get(chunks.size()-1);
300                         simulationStatuses[index] = status;
301
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));
307                                 updateProgress();
308                                 return;
309                         }
310                         
311                         if (simulationStage == -2) {
312                                 simulationStage++;
313                                 burnoutVelocity = MathUtil.max(status.velocity.z, 0.1);
314                                 System.out.println("CHANGING to Method 2, vel="+burnoutVelocity);
315                         }
316                         
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=" +
320                                                 burnoutVelocity);
321                                 setSimulationProgress(MathUtil.map(status.velocity.z, burnoutVelocity, 0,
322                                                 BURNOUT_PROGRESS, APOGEE_PROGRESS));
323                                 updateProgress();
324                                 return;
325                         }
326                         
327                         if (simulationStage == -1 && status.velocity.z < 0) {
328                                 simulationStage++;
329                                 apogeeAltitude = status.position.z;
330                         }
331                         
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));
337                         updateProgress();
338                 }
339                 
340                 /**
341                  * Marks this simulation as done and calls the progress update.
342                  */
343                 @Override
344                 protected void simulationDone() {
345                         simulationDone[index] = true;
346                         System.out.println("DONE, setting progress");
347                         setSimulationProgress(1.0);
348                         updateProgress();
349                 }
350                 
351                 
352                 /**
353                  * Marks the simulation as done and shows a dialog presenting
354                  * the error, unless the simulation was cancelled.
355                  */
356                 @Override
357                 protected void simulationInterrupted(Throwable t) {
358                         
359                         if (t instanceof SimulationCancelledException) {
360                                 simulationDone();
361                                 return;  // Ignore cancellations
362                         }
363                         
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();
369                         
370                         // Analyze the exception type
371                         if (t instanceof SimulationLaunchException) {
372                                 
373                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
374                                                 new Object[] {
375                                                 "Unable to simulate:",
376                                                 t.getMessage()
377                                                 },
378                                                 null, simulation.getName(), JOptionPane.ERROR_MESSAGE);
379                                 
380                         } else if (t instanceof SimulationException) {
381                                 
382                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
383                                                 new Object[] {
384                                                 "A error occurred during the simulation:",
385                                                 t.getMessage()
386                                                 }, 
387                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
388                                 
389                         } else if (t instanceof Exception) {
390                                 
391                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
392                                                 new Object[] {
393                                                 "An exception occurred during the simulation:",
394                                                 t.getMessage(),
395                                                 simulation.getSimulationListeners().isEmpty() ? 
396                                                 "Please report this as a bug along with the details below." : ""
397                                                 }, 
398                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
399                                 
400                         } else if (t instanceof AssertionError) {
401                                 
402                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
403                                                 new Object[] {
404                                                         "A computation error occurred during the simulation.",
405                                                         "Please report this as a bug along with the details below."
406                                                 }, 
407                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
408                                 
409                         } else {
410                                 
411                                 // Probably an Error
412                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this, 
413                                                 new Object[] {
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!"
417                                                 }, 
418                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
419                                 
420                         }
421                         simulationDone();
422                 }
423                 
424                 
425
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);
432                 }
433
434
435                 /**
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.
438                  * 
439                  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
440                  */
441                 private class SimulationProgressListener extends AbstractSimulationListener {
442                         private long time = 0;
443
444                         @Override
445                         public Collection<FlightEvent> handleEvent(FlightEvent event,
446                                         SimulationStatus status) {
447                                 
448                                 switch (event.getType()) {
449                                 case APOGEE:
450                                         simulationStage = 0;
451                                         apogeeAltitude = status.position.z;
452                                         System.out.println("APOGEE, setting progress");
453                                         setSimulationProgress(APOGEE_PROGRESS);
454                                         publish(status);
455                                         break;
456                                         
457                                 case LAUNCH:
458                                         publish(status);
459                                         break;
460                                         
461                                 case SIMULATION_END:
462                                         System.out.println("END, setting progress");
463                                         setSimulationProgress(1.0);
464                                         break;
465                                 }
466                                 return null;
467                         }
468
469                         @Override
470                         public Collection<FlightEvent> stepTaken(SimulationStatus status) {
471                                 if (System.currentTimeMillis() >= time + UPDATE_MS) {
472                                         time = System.currentTimeMillis();
473                                         publish(status);
474                                 }
475                                 return null;
476                         }
477                 }
478         }
479 }