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