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