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