]> git.gag.com Git - debian/openrocket/blob - src/net/sf/openrocket/gui/main/SimulationRunDialog.java
bug fixes
[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         private final Simulation[] simulations;
73         private final SimulationWorker[] simulationWorkers;
74         private final SimulationStatus[] simulationStatuses;
75         private final double[] simulationMaxAltitude;
76         private final double[] simulationMaxVelocity;
77         private final boolean[] simulationDone;
78         
79         public SimulationRunDialog(Window window, Simulation... simulations) {
80                 super(window, "Running simulations...", Dialog.ModalityType.DOCUMENT_MODAL);
81                 
82                 if (simulations.length == 0) {
83                         throw new IllegalArgumentException("Called with no simulations to run");
84                 }
85                 
86                 this.simulations = simulations;
87                 
88                 // Initialize the simulations
89                 int n = simulations.length;
90                 simulationWorkers = new SimulationWorker[n];
91                 simulationStatuses = new SimulationStatus[n];
92                 simulationMaxAltitude = new double[n];
93                 simulationMaxVelocity = new double[n];
94                 simulationDone = new boolean[n];
95                 
96                 for (int i = 0; i < n; i++) {
97                         simulationWorkers[i] = new InteractiveSimulationWorker(simulations[i], i);
98                         executor.execute(simulationWorkers[i]);
99                 }
100                 
101                 // Build the dialog
102                 JPanel panel = new JPanel(new MigLayout("fill", "[][grow]"));
103                 
104                 simLabel = new JLabel("Running ...");
105                 panel.add(simLabel, "spanx, wrap para");
106                 
107                 panel.add(new JLabel("Simulation time: "), "gapright para");
108                 timeLabel = new JLabel("");
109                 panel.add(timeLabel, "growx, wrap rel");
110                 
111                 panel.add(new JLabel("Altitude: "));
112                 altLabel = new JLabel("");
113                 panel.add(altLabel, "growx, wrap rel");
114                 
115                 panel.add(new JLabel("Velocity: "));
116                 velLabel = new JLabel("");
117                 panel.add(velLabel, "growx, wrap para");
118                 
119                 progressBar = new JProgressBar();
120                 panel.add(progressBar, "spanx, growx, wrap para");
121                 
122
123                 // Add cancel button
124                 JButton cancel = new JButton("Cancel");
125                 cancel.addActionListener(new ActionListener() {
126                         @Override
127                         public void actionPerformed(ActionEvent e) {
128                                 cancelSimulations();
129                         }
130                 });
131                 panel.add(cancel, "spanx, tag cancel");
132                 
133
134                 // Cancel simulations when user closes the window
135                 this.addWindowListener(new WindowAdapter() {
136                         @Override
137                         public void windowClosing(WindowEvent e) {
138                                 cancelSimulations();
139                         }
140                 });
141                 
142
143                 this.add(panel);
144                 this.setMinimumSize(new Dimension(300, 0));
145                 this.setLocationByPlatform(true);
146                 this.validate();
147                 this.pack();
148                 
149                 GUIUtil.setDisposableDialogOptions(this, null);
150                 
151                 updateProgress();
152         }
153         
154         
155         /**
156          * Cancel the currently running simulations.  This is equivalent to clicking
157          * the Cancel button on the dialog.
158          */
159         public void cancelSimulations() {
160                 executor.shutdownNow();
161                 for (SimulationWorker w : simulationWorkers) {
162                         w.cancel(true);
163                 }
164         }
165         
166         
167         /**
168          * Static helper method to run simulations.
169          * 
170          * @param parent                the parent Window of the dialog to use.
171          * @param simulations   the simulations to run.
172          */
173         public static void runSimulations(Window parent, Simulation... simulations) {
174                 new SimulationRunDialog(parent, simulations).setVisible(true);
175         }
176         
177         
178
179
180         private void updateProgress() {
181                 int index;
182                 for (index = 0; index < simulations.length; index++) {
183                         if (!simulationDone[index])
184                                 break;
185                 }
186                 
187                 if (index >= simulations.length) {
188                         // Everything is done, close the dialog
189                         log.debug("Everything done.");
190                         this.dispose();
191                         return;
192                 }
193                 
194                 // Update the progress bar status
195                 int progress = 0;
196                 for (SimulationWorker s : simulationWorkers) {
197                         progress += s.getProgress();
198                 }
199                 progress /= simulationWorkers.length;
200                 progressBar.setValue(progress);
201                 log.debug("Progressbar value " + progress);
202                 
203                 // Update the simulation fields
204                 simLabel.setText("Running " + simulations[index].getName());
205                 if (simulationStatuses[index] == null) {
206                         log.debug("No simulation status data available, setting empty labels");
207                         timeLabel.setText("");
208                         altLabel.setText("");
209                         velLabel.setText("");
210                         return;
211                 }
212                 
213                 Unit u = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
214                 timeLabel.setText(u.toStringUnit(simulationStatuses[index].getSimulationTime()));
215                 
216                 u = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
217                 altLabel.setText(u.toStringUnit(simulationStatuses[index].getRocketPosition().z) + " (max. " +
218                                 u.toStringUnit(simulationMaxAltitude[index]) + ")");
219                 
220                 u = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
221                 velLabel.setText(u.toStringUnit(simulationStatuses[index].getRocketVelocity().z) + " (max. " +
222                                 u.toStringUnit(simulationMaxVelocity[index]) + ")");
223         }
224         
225         
226
227         /**
228          * A SwingWorker that performs a flight simulation.  It periodically updates the
229          * simulation statuses of the parent class and calls updateProgress().
230          * The progress of the simulation is stored in the progress property of the
231          * SwingWorker.
232          * 
233          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
234          */
235         private class InteractiveSimulationWorker extends SimulationWorker {
236                 
237                 private final int index;
238                 private final double burnoutTimeEstimate;
239                 private volatile double burnoutVelocity;
240                 private volatile double apogeeAltitude;
241                 
242                 /*
243                  * -2 = time from 0 ... burnoutTimeEstimate
244                  * -1 = velocity from v(burnoutTimeEstimate) ... 0
245                  *  0 ... n = stages from alt(max) ... 0
246                  */
247                 private volatile int simulationStage = -2;
248                 
249                 private int progress = 0;
250                 
251                 
252                 public InteractiveSimulationWorker(Simulation sim, int index) {
253                         super(sim);
254                         this.index = index;
255                         
256                         // Calculate estimate of motor burn time
257                         double launchBurn = 0;
258                         double otherBurn = 0;
259                         Configuration config = simulation.getConfiguration();
260                         String id = simulation.getConditions().getMotorConfigurationID();
261                         Iterator<MotorMount> iterator = config.motorIterator();
262                         while (iterator.hasNext()) {
263                                 MotorMount m = iterator.next();
264                                 if (m.getIgnitionEvent() == IgnitionEvent.LAUNCH)
265                                         launchBurn = MathUtil.max(launchBurn, m.getMotor(id).getBurnTimeEstimate());
266                                 else
267                                         otherBurn = otherBurn + m.getMotor(id).getBurnTimeEstimate();
268                         }
269                         burnoutTimeEstimate = Math.max(launchBurn + otherBurn, 0.1);
270                         
271                 }
272                 
273                 
274                 /**
275                  * Return the extra listeners to use, a progress listener and cancel listener.
276                  */
277                 @Override
278                 protected SimulationListener[] getExtraListeners() {
279                         return new SimulationListener[] { new SimulationProgressListener() };
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.getRocketPosition().z);
294                                 simulationMaxVelocity[index] = Math.max(simulationMaxVelocity[index],
295                                                 s.getRocketVelocity().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.getSimulationTime() < burnoutTimeEstimate) {
304                                 log.debug("Method 1:  t=" + status.getSimulationTime() + "  est=" + burnoutTimeEstimate);
305                                 setSimulationProgress(MathUtil.map(status.getSimulationTime(), 0, burnoutTimeEstimate,
306                                                 0.0, BURNOUT_PROGRESS));
307                                 updateProgress();
308                                 return;
309                         }
310                         
311                         if (simulationStage == -2) {
312                                 simulationStage++;
313                                 burnoutVelocity = MathUtil.max(status.getRocketVelocity().z, 0.1);
314                                 log.debug("CHANGING to Method 2, vel=" + burnoutVelocity);
315                         }
316                         
317                         // 2. z-velocity from burnout velocity to zero
318                         if (simulationStage == -1 && status.getRocketVelocity().z >= 0) {
319                                 log.debug("Method 2:  vel=" + status.getRocketVelocity().z + " burnout=" + burnoutVelocity);
320                                 setSimulationProgress(MathUtil.map(status.getRocketVelocity().z, burnoutVelocity, 0,
321                                                 BURNOUT_PROGRESS, APOGEE_PROGRESS));
322                                 updateProgress();
323                                 return;
324                         }
325                         
326                         if (simulationStage == -1 && status.getRocketVelocity().z < 0) {
327                                 simulationStage++;
328                                 apogeeAltitude = MathUtil.max(status.getRocketPosition().z, 1);
329                                 log.debug("CHANGING to Method 3, apogee=" + apogeeAltitude);
330                         }
331                         
332                         // 3. z-position from apogee to zero
333                         // TODO: MEDIUM: several stages
334                         log.debug("Method 3:  alt=" + status.getRocketPosition().z + "  apogee=" + apogeeAltitude);
335                         setSimulationProgress(MathUtil.map(status.getRocketPosition().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                         log.debug("Simulation done");
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                                 // TODO: MEDIUM: Check the exception handling here...
392                                 t.printStackTrace();
393                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
394                                                 new Object[] {
395                                                                 "An exception occurred during the simulation:",
396                                                                 t.getMessage(),
397                                                                 simulation.getSimulationListeners().isEmpty() ?
398                                                                                 "Please report this as a bug along with the details below." : ""
399                                                 },
400                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
401                                 
402                         } else if (t instanceof AssertionError) {
403                                 
404                                 t.printStackTrace();
405                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
406                                                 new Object[] {
407                                                                 "A computation error occurred during the simulation.",
408                                                                 "Please report this as a bug along with the details below."
409                                                 },
410                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
411                                 
412                         } else {
413                                 
414                                 // Probably an Error
415                                 DetailDialog.showDetailedMessageDialog(SimulationRunDialog.this,
416                                                 new Object[] {
417                                                                 "An unknown error was encountered during the simulation.",
418                                                                 "The program may be unstable, you should save all your designs " +
419                                                                                 "and restart OpenRocket now!"
420                                                 },
421                                                 stackTrace, simulation.getName(), JOptionPane.ERROR_MESSAGE);
422                                 
423                         }
424                         simulationDone();
425                 }
426                 
427                 
428
429                 private void setSimulationProgress(double p) {
430                         int exact = Math.max(progress, (int) (100 * p + 0.5));
431                         progress = MathUtil.clamp(exact, 0, 100);
432                         log.debug("Setting progress to " + progress + " (real " + exact + ")");
433                         super.setProgress(progress);
434                 }
435                 
436                 
437                 /**
438                  * A simulation listener that regularly updates the progress property of the 
439                  * SimulationWorker and publishes the simulation status for the run dialog to process.
440                  * 
441                  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
442                  */
443                 private class SimulationProgressListener extends AbstractSimulationListener {
444                         private long time = 0;
445                         
446                         @Override
447                         public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) {
448                                 switch (event.getType()) {
449                                 case APOGEE:
450                                         simulationStage = 0;
451                                         apogeeAltitude = status.getRocketPosition().z;
452                                         log.debug("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                                         log.debug("END, setting progress");
463                                         setSimulationProgress(1.0);
464                                         break;
465                                 }
466                                 return true;
467                         }
468                         
469                         @Override
470                         public void postStep(SimulationStatus status) {
471                                 if (System.currentTimeMillis() >= time + UPDATE_MS) {
472                                         time = System.currentTimeMillis();
473                                         publish(status);
474                                 }
475                         }
476                 }
477         }
478 }