SafetyMutex and rocket optimization updates

This commit is contained in:
Sampo Niskanen 2010-10-25 19:02:31 +00:00
parent 833e2f80cf
commit a63f0b4bae
50 changed files with 1683 additions and 322 deletions

View File

@ -10,7 +10,6 @@
</accessrules>
</classpathentry>
<classpathentry kind="lib" path="lib-extra/RXTXcomm.jar"/>
<classpathentry kind="lib" path="lib-test/junit-4.7.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.0.13.jar" sourcepath="/home/sampo/Projects/lib/jfreechart-1.0.13/source"/>
<classpathentry kind="lib" path="lib/jcommon-1.0.16.jar">
<accessrules>
@ -18,5 +17,10 @@
</accessrules>
</classpathentry>
<classpathentry kind="lib" path="lib/miglayout15-swing.jar"/>
<classpathentry kind="lib" path="lib-test/hamcrest-core-1.3.0RC1.jar"/>
<classpathentry kind="lib" path="lib-test/hamcrest-library-1.3.0RC1.jar"/>
<classpathentry kind="lib" path="lib-test/jmock-2.6.0-RC2.jar"/>
<classpathentry kind="lib" path="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
<classpathentry kind="lib" path="lib-test/junit-dep-4.8.2.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -1,3 +1,15 @@
2010-10-25 Doug Pedrick
* [BUG] Take launch lug radial angle into account when loading rkt file
2010-10-24 Sampo Niskane
* Added SafetyMutex and took into use in Simulation
2010-10-18 Sampo Niskanen
* Ignore Sun JRE bug in D3D
2010-10-09 Sampo Niskanen
* [BUG] Fixed conversion to freeform fin set

View File

@ -36,8 +36,11 @@
<pathelement location="${build-test.dir}"/>
<pathelement location="${classes.dir}"/>
<pathelement location="${src-test.dir}"/>
<!-- <pathelement location="${ant.library.dir}/junit4.jar"/> -->
<pathelement location="lib-test/junit-4.7.jar"/>
<pathelement location="lib-test/junit-dep-4.8.2.jar"/>
<pathelement location="lib-test/hamcrest-core-1.3.0RC1.jar"/>
<pathelement location="lib-test/hamcrest-library-1.3.0RC1.jar"/>
<pathelement location="lib-test/jmock-2.6.0-RC2.jar"/>
<pathelement location="lib-test/jmock-junit4-2.6.0-RC2.jar"/>
</path>

View File

@ -25,6 +25,14 @@ openrocket.log.tracelevel
Debugging options
-----------------
openrocket.debug
Turns on various options useful for debugging purposes. The parameters defined are:
openrocket.log.stdout=VBOSE
openrocket.log.tracelevel=VBOSE
openrocket.debug.menu=true
openrocket.debug.motordigest=true
openrocket.debug.menu
If defined the "Debug" menu will be displayed in the main application window.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -27,8 +27,16 @@ import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.SafetyMutex;
/**
* A class defining a simulation, its conditions and simulated data.
* <p>
* This class is not thread-safe and enforces single-threaded access with a
* SafetyMutex.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class Simulation implements ChangeSource, Cloneable {
private static final LogHelper log = Application.getLogger();
@ -49,6 +57,7 @@ public class Simulation implements ChangeSource, Cloneable {
NOT_SIMULATED
}
private final SafetyMutex mutex = new SafetyMutex();
private final Rocket rocket;
@ -146,6 +155,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return the rocket.
*/
public Rocket getRocket() {
mutex.verify();
return rocket;
}
@ -157,6 +167,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return a newly created Configuration of the launch conditions.
*/
public Configuration getConfiguration() {
mutex.verify();
Configuration c = new Configuration(rocket);
c.setMotorConfigurationID(conditions.getMotorConfigurationID());
c.setAllStages();
@ -171,6 +182,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return the simulation conditions.
*/
public GUISimulationConditions getConditions() {
mutex.verify();
return conditions;
}
@ -182,6 +194,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return the actual list of simulation listeners.
*/
public List<String> getSimulationListeners() {
mutex.verify();
return simulationListeners;
}
@ -192,6 +205,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return the name for the simulation.
*/
public String getName() {
mutex.verify();
return name;
}
@ -202,15 +216,20 @@ public class Simulation implements ChangeSource, Cloneable {
* @param name the name of the simulation.
*/
public void setName(String name) {
if (this.name.equals(name))
return;
if (name == null)
this.name = "";
else
this.name = name;
fireChangeEvent();
mutex.lock("setName");
try {
if (this.name.equals(name))
return;
if (name == null)
this.name = "";
else
this.name = name;
fireChangeEvent();
} finally {
mutex.unlock("setName");
}
}
@ -222,6 +241,8 @@ public class Simulation implements ChangeSource, Cloneable {
* @see Status
*/
public Status getStatus() {
mutex.verify();
if (status == Status.UPTODATE || status == Status.LOADED) {
if (rocket.getFunctionalModID() != simulatedRocketID ||
!conditions.equals(simulatedConditions))
@ -236,54 +257,59 @@ public class Simulation implements ChangeSource, Cloneable {
public void simulate(SimulationListener... additionalListeners)
throws SimulationException {
if (this.status == Status.EXTERNAL) {
throw new SimulationException("Cannot simulate imported simulation.");
}
SimulationEngine simulator;
mutex.lock("simulate");
try {
simulator = simulationEngineClass.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("Cannot instantiate simulator.", e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
} catch (NullPointerException e) {
throw new IllegalStateException("Simulator null", e);
}
SimulationConditions simulationConditions = conditions.toSimulationConditions();
for (SimulationListener l : additionalListeners) {
simulationConditions.getSimulationListenerList().add(l);
}
for (String className : simulationListeners) {
SimulationListener l = null;
try {
Class<?> c = Class.forName(className);
l = (SimulationListener) c.newInstance();
} catch (Exception e) {
throw new SimulationListenerException("Could not instantiate listener of " +
"class: " + className, e);
if (this.status == Status.EXTERNAL) {
throw new SimulationException("Cannot simulate imported simulation.");
}
simulationConditions.getSimulationListenerList().add(l);
SimulationEngine simulator;
try {
simulator = simulationEngineClass.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("Cannot instantiate simulator.", e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Cannot access simulator instance?! BUG!", e);
} catch (NullPointerException e) {
throw new IllegalStateException("Simulator null", e);
}
SimulationConditions simulationConditions = conditions.toSimulationConditions();
for (SimulationListener l : additionalListeners) {
simulationConditions.getSimulationListenerList().add(l);
}
for (String className : simulationListeners) {
SimulationListener l = null;
try {
Class<?> c = Class.forName(className);
l = (SimulationListener) c.newInstance();
} catch (Exception e) {
throw new SimulationListenerException("Could not instantiate listener of " +
"class: " + className, e);
}
simulationConditions.getSimulationListenerList().add(l);
}
long t1, t2;
log.debug("Simulation: calling simulator");
t1 = System.currentTimeMillis();
simulatedData = simulator.simulate(simulationConditions);
t2 = System.currentTimeMillis();
log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
// Set simulated info after simulation, will not be set in case of exception
simulatedConditions = conditions.clone();
simulatedMotors = getConfiguration().getMotorConfigurationDescription();
simulatedRocketID = rocket.getFunctionalModID();
status = Status.UPTODATE;
fireChangeEvent();
} finally {
mutex.unlock("simulate");
}
long t1, t2;
log.debug("Simulation: calling simulator");
t1 = System.currentTimeMillis();
simulatedData = simulator.simulate(simulationConditions);
t2 = System.currentTimeMillis();
log.debug("Simulation: returning from simulator, simulation took " + (t2 - t1) + "ms");
// Set simulated info after simulation, will not be set in case of exception
simulatedConditions = conditions.clone();
simulatedMotors = getConfiguration().getMotorConfigurationDescription();
simulatedRocketID = rocket.getFunctionalModID();
status = Status.UPTODATE;
fireChangeEvent();
}
@ -294,6 +320,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return the conditions used in the previous simulation, or <code>null</code>.
*/
public GUISimulationConditions getSimulatedConditions() {
mutex.verify();
return simulatedConditions;
}
@ -306,6 +333,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @see FlightData#getWarningSet()
*/
public WarningSet getSimulatedWarnings() {
mutex.verify();
if (simulatedData == null)
return null;
return simulatedData.getWarningSet();
@ -321,6 +349,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @see Rocket#getMotorConfigurationNameOrDescription(String)
*/
public String getSimulatedMotorDescription() {
mutex.verify();
return simulatedMotors;
}
@ -331,6 +360,7 @@ public class Simulation implements ChangeSource, Cloneable {
* @return the flight data of the previous simulation, or <code>null</code>.
*/
public FlightData getSimulatedData() {
mutex.verify();
return simulatedData;
}
@ -344,6 +374,7 @@ public class Simulation implements ChangeSource, Cloneable {
*/
@SuppressWarnings("unchecked")
public Simulation copy() {
mutex.lock("copy");
try {
Simulation copy = (Simulation) super.clone();
@ -359,9 +390,10 @@ public class Simulation implements ChangeSource, Cloneable {
return copy;
} catch (CloneNotSupportedException e) {
throw new BugException("Clone not supported, BUG", e);
} finally {
mutex.unlock("copy");
}
}
@ -375,26 +407,33 @@ public class Simulation implements ChangeSource, Cloneable {
*/
@SuppressWarnings("unchecked")
public Simulation duplicateSimulation(Rocket newRocket) {
Simulation copy = new Simulation(newRocket);
copy.name = this.name;
copy.conditions.copyFrom(this.conditions);
copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
return copy;
mutex.lock("duplicateSimulation");
try {
Simulation copy = new Simulation(newRocket);
copy.name = this.name;
copy.conditions.copyFrom(this.conditions);
copy.simulationListeners = (ArrayList<String>) this.simulationListeners.clone();
copy.simulationStepperClass = this.simulationStepperClass;
copy.aerodynamicCalculatorClass = this.aerodynamicCalculatorClass;
return copy;
} finally {
mutex.unlock("duplicateSimulation");
}
}
@Override
public void addChangeListener(ChangeListener listener) {
mutex.verify();
listeners.add(listener);
}
@Override
public void removeChangeListener(ChangeListener listener) {
mutex.verify();
listeners.remove(listener);
}

View File

@ -141,7 +141,8 @@ public class DebugLogDialog extends JDialog {
panel.add(new JLabel("Display log lines:"), "gapright para, split");
for (LogLevel l : LogLevel.values()) {
JCheckBox box = new JCheckBox(l.toString());
box.setSelected(true);
// By default display DEBUG and above
box.setSelected(l.atLeast(LogLevel.DEBUG));
box.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
@ -279,6 +280,7 @@ public class DebugLogDialog extends JDialog {
sorter.setComparator(1, NumericComparator.INSTANCE);
sorter.setComparator(4, new LocationComparator());
table.setRowSorter(sorter);
sorter.setRowFilter(new LogFilter());
panel.add(new JScrollPane(table), "span, grow, width " +

View File

@ -3,16 +3,31 @@ package net.sf.openrocket.gui.dialogs;
import java.awt.Component;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import net.sf.openrocket.util.GUIUtil;
public class DetailDialog {
public static void showDetailedMessageDialog(Component parentComponent, Object message,
String details, String title, int messageType) {
public static void showDetailedMessageDialog(Component parentComponent, Object message,
String details, String title, int messageType) {
// TODO: HIGH: Detailed dialog
JOptionPane.showMessageDialog(parentComponent, message, title, messageType, null);
if (details != null) {
JTextArea textArea = null;
textArea = new JTextArea(5, 40);
textArea.setText(details);
textArea.setCaretPosition(0);
textArea.setEditable(false);
GUIUtil.changeFontSize(textArea, -2);
JOptionPane.showMessageDialog(parentComponent,
new Object[] { message, new JScrollPane(textArea) },
title, messageType, null);
} else {
JOptionPane.showMessageDialog(parentComponent, message, title, messageType, null);
}
}
}

View File

@ -12,15 +12,15 @@ import java.util.ServiceLoader;
import javax.swing.JDialog;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationParameter;
import net.sf.openrocket.optimization.rocketoptimization.RocketOptimizationParameterService;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameterService;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifierService;
import net.sf.openrocket.util.BugException;
public class GeneralOptimizationDialog extends JDialog {
private final List<RocketOptimizationParameter> optimizationParameters = new ArrayList<RocketOptimizationParameter>();
private final List<OptimizableParameter> optimizationParameters = new ArrayList<OptimizableParameter>();
private final Map<Object, List<SimulationModifier>> simulationModifiers =
new HashMap<Object, List<SimulationModifier>>();
@ -36,10 +36,10 @@ public class GeneralOptimizationDialog extends JDialog {
private void loadOptimizationParameters() {
ServiceLoader<RocketOptimizationParameterService> loader =
ServiceLoader.load(RocketOptimizationParameterService.class);
ServiceLoader<OptimizableParameterService> loader =
ServiceLoader.load(OptimizableParameterService.class);
for (RocketOptimizationParameterService g : loader) {
for (OptimizableParameterService g : loader) {
optimizationParameters.addAll(g.getParameters(document));
}
@ -47,9 +47,9 @@ public class GeneralOptimizationDialog extends JDialog {
throw new BugException("No rocket optimization parameters found, distribution built wrong.");
}
Collections.sort(optimizationParameters, new Comparator<RocketOptimizationParameter>() {
Collections.sort(optimizationParameters, new Comparator<OptimizableParameter>() {
@Override
public int compare(RocketOptimizationParameter o1, RocketOptimizationParameter o2) {
public int compare(OptimizableParameter o1, OptimizableParameter o2) {
return o1.getName().compareTo(o2.getName());
}
});

View File

@ -25,6 +25,8 @@ import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.swing.Action;
@ -69,6 +71,7 @@ import net.sf.openrocket.gui.dialogs.AboutDialog;
import net.sf.openrocket.gui.dialogs.BugReportDialog;
import net.sf.openrocket.gui.dialogs.ComponentAnalysisDialog;
import net.sf.openrocket.gui.dialogs.DebugLogDialog;
import net.sf.openrocket.gui.dialogs.DetailDialog;
import net.sf.openrocket.gui.dialogs.ExampleDesignDialog;
import net.sf.openrocket.gui.dialogs.LicenseDialog;
import net.sf.openrocket.gui.dialogs.MotorDatabaseLoadingDialog;
@ -87,6 +90,8 @@ import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.GUIUtil;
import net.sf.openrocket.util.Icons;
import net.sf.openrocket.util.MemoryManagement;
import net.sf.openrocket.util.MemoryManagement.MemoryData;
import net.sf.openrocket.util.OpenFileWorker;
import net.sf.openrocket.util.Prefs;
import net.sf.openrocket.util.Reflection;
@ -728,10 +733,86 @@ public class BasicFrame extends JFrame {
});
menu.add(item);
menu.addSeparator();
item = new JMenuItem("Memory statistics");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.user("Memory statistics selected");
// Get discarded but remaining objects (this also runs System.gc multiple times)
List<MemoryData> objects = MemoryManagement.getRemainingObjects();
StringBuilder sb = new StringBuilder();
sb.append("Objects that should have been garbage-collected but have not been:\n");
int count = 0;
for (MemoryData data : objects) {
Object o = data.getReference().get();
if (o == null)
continue;
sb.append("Age ").append(System.currentTimeMillis() - data.getRegistrationTime())
.append(" ms: ").append(o).append('\n');
count++;
// Explicitly null the strong reference to avoid possibility of invisible references
o = null;
}
sb.append("Total: " + count);
// Get basic memory stats
System.gc();
long max = Runtime.getRuntime().maxMemory();
long free = Runtime.getRuntime().freeMemory();
long used = max - free;
String[] stats = new String[4];
stats[0] = "Memory usage:";
stats[1] = String.format(" Max memory: %.1f MB", max / 1024.0 / 1024.0);
stats[2] = String.format(" Used memory: %.1f MB (%.0f%%)", used / 1024.0 / 1024.0, 100.0 * used / max);
stats[3] = String.format(" Free memory: %.1f MB (%.0f%%)", free / 1024.0 / 1024.0, 100.0 * free / max);
DetailDialog.showDetailedMessageDialog(BasicFrame.this, stats, sb.toString(),
"Memory statistics", JOptionPane.INFORMATION_MESSAGE);
}
});
menu.add(item);
item = new JMenuItem("Exhaust memory");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.user("Exhaust memory selected");
LinkedList<byte[]> data = new LinkedList<byte[]>();
int count = 0;
final int bytesPerArray = 10240;
try {
while (true) {
byte[] array = new byte[bytesPerArray];
for (int i = 0; i < bytesPerArray; i++) {
array[i] = (byte) i;
}
data.add(array);
count++;
}
} catch (OutOfMemoryError error) {
data = null;
long size = bytesPerArray * (long) count;
String s = String.format("OutOfMemory occurred after %d iterations (approx. %.1f MB consumed)",
count, size / 1024.0 / 1024.0);
log.debug(s, error);
JOptionPane.showMessageDialog(BasicFrame.this, s);
}
}
});
menu.add(item);
menu.addSeparator();
item = new JMenuItem("Exception here");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.user("Exception here selected");
throw new RuntimeException("Testing exception from menu action listener");
@ -769,13 +850,22 @@ public class BasicFrame extends JFrame {
});
menu.add(item);
item = new JMenuItem("OutOfMemoryError here");
item.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
log.user("OutOfMemoryError here selected");
throw new OutOfMemoryError("Testing OutOfMemoryError from menu action listener");
}
});
menu.add(item);
return menu;
}
/**
* Select the tab on the main pane.
*

View File

@ -5,8 +5,8 @@ import javax.swing.SwingUtilities;
import net.sf.openrocket.gui.dialogs.BugReportDialog;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.logging.TraceException;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@ -63,6 +63,7 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
} else {
log.info("Exception handler not on EDT, invoking dialog on EDT");
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
showDialog(thread, throwable);
}
@ -76,7 +77,7 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
log.error("Caught exception while handling exception", ex);
System.err.println("Exception in exception handler, dumping exception:");
ex.printStackTrace();
} catch (Throwable ignore) {
} catch (Exception ignore) {
}
} finally {
@ -90,11 +91,14 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
* <p>
* This method is guaranteed never to throw an exception, and can thus be safely
* used in finally blocks.
*
* @param message the error message.
*/
public static void handleErrorCondition(String message) {
log.error(1, message);
log.error(1, message, new TraceException());
handleErrorCondition(new InternalException(message));
}
@ -102,6 +106,9 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
* <p>
* This method is guaranteed never to throw an exception, and can thus be safely
* used in finally blocks.
*
* @param message the error message.
* @param exception the exception that occurred.
@ -115,36 +122,39 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
/**
* Handle an error condition programmatically without throwing an exception.
* This can be used in cases where recovery of the error is desirable.
* <p>
* This method is guaranteed never to throw an exception, and can thus be safely
* used in finally blocks.
*
* @param exception the exception that occurred.
*/
public static void handleErrorCondition(final Exception exception) {
if (!(exception instanceof InternalException)) {
log.error(1, "Error occurred", exception);
}
final Thread thread = Thread.currentThread();
final ExceptionHandler handler = instance;
if (handler == null) {
// Not initialized, throw the exception
throw new BugException("Error condition before exception handling has been initialized", exception);
}
try {
if (!(exception instanceof InternalException)) {
log.error(1, "Error occurred", exception);
}
final Thread thread = Thread.currentThread();
final ExceptionHandler handler = instance;
if (handler == null) {
log.error("Error condition occurred before exception handling has been initialized", exception);
return;
}
if (SwingUtilities.isEventDispatchThread()) {
log.info("Running in EDT, showing dialog");
handler.showDialog(thread, exception);
} else {
log.info("Not in EDT, invoking and waiting for dialog");
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
handler.showDialog(thread, exception);
}
});
}
} catch (Exception e) {
log.error("Exception occurred while showing error dialog", e);
e.printStackTrace();
log.error("Exception occurred in error handler", e);
}
}
@ -289,6 +299,8 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
*/
private static boolean isNonFatalJREBug(Throwable t) {
// NOTE: Calling method logs the entire throwable, so log only message here
/*
* Detect and ignore bug 6828938 in Sun JRE 1.6.0_14 - 1.6.0_16.
*/
@ -346,6 +358,18 @@ public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
return true;
}
}
/*
* Detect Sun JRE bug in D3D
*/
if (t instanceof ClassCastException) {
if (t.getMessage().equals("sun.awt.Win32GraphicsConfig cannot be cast to sun.java2d.d3d.D3DGraphicsConfig")) {
log.warn("Ignoring Sun JRE bug " +
"(see http://forums.sun.com/thread.jspa?threadID=5440525): " + t);
return true;
}
}
return false;
}

View File

@ -226,7 +226,6 @@ public class SimulationPanel extends JPanel {
// Set simulation status icon
Simulation.Status status = document.getSimulation(row).getStatus();
System.out.println("status=" + status);
label.setIcon(Icons.SIMULATION_STATUS_ICON_MAP.get(status));

View File

@ -69,7 +69,13 @@ public class SimulationRunDialog extends JDialog {
private final JProgressBar progressBar;
/*
* NOTE: Care must be used when accessing the simulation parameters, since they
* are being run in another thread. Mutexes are used to avoid concurrent usage, which
* will result in an exception being thrown!
*/
private final Simulation[] simulations;
private final String[] simulationNames;
private final SimulationWorker[] simulationWorkers;
private final SimulationStatus[] simulationStatuses;
private final double[] simulationMaxAltitude;
@ -87,6 +93,7 @@ public class SimulationRunDialog extends JDialog {
// Initialize the simulations
int n = simulations.length;
simulationNames = new String[n];
simulationWorkers = new SimulationWorker[n];
simulationStatuses = new SimulationStatus[n];
simulationMaxAltitude = new double[n];
@ -94,6 +101,7 @@ public class SimulationRunDialog extends JDialog {
simulationDone = new boolean[n];
for (int i = 0; i < n; i++) {
simulationNames[i] = simulations[i].getName();
simulationWorkers[i] = new InteractiveSimulationWorker(simulations[i], i);
executor.execute(simulationWorkers[i]);
}
@ -201,7 +209,7 @@ public class SimulationRunDialog extends JDialog {
log.debug("Progressbar value " + progress);
// Update the simulation fields
simLabel.setText("Running " + simulations[index].getName());
simLabel.setText("Running " + simulationNames[index]);
if (simulationStatuses[index] == null) {
log.debug("No simulation status data available, setting empty labels");
timeLabel.setText("");

View File

@ -54,8 +54,6 @@ public abstract class SimulationWorker extends SwingWorker<FlightData, Simulatio
try {
simulation.simulate(listeners);
} catch (Throwable e) {
// System.out.println("Simulation interrupted:");
// e.printStackTrace();
throwable = e;
return null;
}
@ -77,8 +75,6 @@ public abstract class SimulationWorker extends SwingWorker<FlightData, Simulatio
/**
* Called after a simulation is successfully simulated. This method is not
* called if the simulation ends in an exception.
*
* @param sim the simulation including the flight data
*/
protected abstract void simulationDone();

View File

@ -608,7 +608,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
@Override
protected void simulationDone() {
// Do nothing if cancelled
if (isCancelled() || backgroundSimulationWorker != this) // Double-check
if (isCancelled() || backgroundSimulationWorker != this)
return;
backgroundSimulationWorker = null;
@ -658,6 +658,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
* Updates the selection in the FigureParameters and repaints the figure.
* Ignores the event itself.
*/
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath[] paths = selectionModel.getSelectionPaths();
if (paths == null) {
@ -688,6 +689,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
figure.addChangeListener(this);
}
@Override
public void actionPerformed(ActionEvent e) {
boolean state = (Boolean) getValue(Action.SELECTED_KEY);
if (state == true) {
@ -698,6 +700,7 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change
stateChanged(null);
}
@Override
public void stateChanged(ChangeEvent e) {
putValue(Action.SELECTED_KEY, figure.getType() == type);
}

View File

@ -16,6 +16,9 @@ import net.sf.openrocket.util.BugException;
* <li><code>message</code> the String message (may be null).
* <li><code>cause</code> the exception that caused this log (may be null).
* </ul>
* <p>
* The logging methods are guaranteed never to throw an exception, and can thus be safely
* used in finally blocks.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
@ -56,7 +59,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void verbose(String message) {
log(createLogLine(0, LogLevel.VBOSE, message, null));
try {
log(createLogLine(0, LogLevel.VBOSE, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -66,7 +73,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void verbose(String message, Throwable cause) {
log(createLogLine(0, LogLevel.VBOSE, message, cause));
try {
log(createLogLine(0, LogLevel.VBOSE, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -76,7 +87,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void verbose(int levels, String message) {
log(createLogLine(levels, LogLevel.VBOSE, message, null));
try {
log(createLogLine(levels, LogLevel.VBOSE, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -87,7 +102,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void verbose(int levels, String message, Throwable cause) {
log(createLogLine(levels, LogLevel.VBOSE, message, cause));
try {
log(createLogLine(levels, LogLevel.VBOSE, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
@ -97,7 +116,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void debug(String message) {
log(createLogLine(0, LogLevel.DEBUG, message, null));
try {
log(createLogLine(0, LogLevel.DEBUG, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -107,7 +130,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void debug(String message, Throwable cause) {
log(createLogLine(0, LogLevel.DEBUG, message, cause));
try {
log(createLogLine(0, LogLevel.DEBUG, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -117,7 +144,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void debug(int levels, String message) {
log(createLogLine(levels, LogLevel.DEBUG, message, null));
try {
log(createLogLine(levels, LogLevel.DEBUG, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -128,7 +159,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void debug(int levels, String message, Throwable cause) {
log(createLogLine(levels, LogLevel.DEBUG, message, cause));
try {
log(createLogLine(levels, LogLevel.DEBUG, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
@ -138,7 +173,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void info(String message) {
log(createLogLine(0, LogLevel.INFO, message, null));
try {
log(createLogLine(0, LogLevel.INFO, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -148,7 +187,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void info(String message, Throwable cause) {
log(createLogLine(0, LogLevel.INFO, message, cause));
try {
log(createLogLine(0, LogLevel.INFO, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -158,7 +201,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void info(int levels, String message) {
log(createLogLine(levels, LogLevel.INFO, message, null));
try {
log(createLogLine(levels, LogLevel.INFO, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -169,7 +216,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void info(int levels, String message, Throwable cause) {
log(createLogLine(levels, LogLevel.INFO, message, cause));
try {
log(createLogLine(levels, LogLevel.INFO, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
@ -179,7 +230,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void user(String message) {
log(createLogLine(0, LogLevel.USER, message, null));
try {
log(createLogLine(0, LogLevel.USER, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -189,7 +244,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void user(String message, Throwable cause) {
log(createLogLine(0, LogLevel.USER, message, cause));
try {
log(createLogLine(0, LogLevel.USER, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -199,7 +258,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void user(int levels, String message) {
log(createLogLine(levels, LogLevel.USER, message, null));
try {
log(createLogLine(levels, LogLevel.USER, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -210,7 +273,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void user(int levels, String message, Throwable cause) {
log(createLogLine(levels, LogLevel.USER, message, cause));
try {
log(createLogLine(levels, LogLevel.USER, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
@ -220,7 +287,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void warn(String message) {
log(createLogLine(0, LogLevel.WARN, message, null));
try {
log(createLogLine(0, LogLevel.WARN, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -230,7 +301,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void warn(String message, Throwable cause) {
log(createLogLine(0, LogLevel.WARN, message, cause));
try {
log(createLogLine(0, LogLevel.WARN, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -240,7 +315,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void warn(int levels, String message) {
log(createLogLine(levels, LogLevel.WARN, message, null));
try {
log(createLogLine(levels, LogLevel.WARN, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -251,7 +330,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void warn(int levels, String message, Throwable cause) {
log(createLogLine(levels, LogLevel.WARN, message, cause));
try {
log(createLogLine(levels, LogLevel.WARN, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
@ -261,7 +344,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void error(String message) {
log(createLogLine(0, LogLevel.ERROR, message, null));
try {
log(createLogLine(0, LogLevel.ERROR, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -271,7 +358,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void error(String message, Throwable cause) {
log(createLogLine(0, LogLevel.ERROR, message, cause));
try {
log(createLogLine(0, LogLevel.ERROR, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -281,7 +372,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void error(int levels, String message) {
log(createLogLine(levels, LogLevel.ERROR, message, null));
try {
log(createLogLine(levels, LogLevel.ERROR, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -292,7 +387,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void error(int levels, String message, Throwable cause) {
log(createLogLine(levels, LogLevel.ERROR, message, cause));
try {
log(createLogLine(levels, LogLevel.ERROR, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
@ -304,7 +403,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void log(LogLevel level, String message) {
log(createLogLine(0, level, message, null));
try {
log(createLogLine(0, level, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -315,7 +418,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void log(LogLevel level, String message, Throwable cause) {
log(createLogLine(0, level, message, cause));
try {
log(createLogLine(0, level, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -326,7 +433,11 @@ public abstract class LogHelper {
* @param message the logged message (may be null).
*/
public void log(int levels, LogLevel level, String message) {
log(createLogLine(levels, level, message, null));
try {
log(createLogLine(levels, level, message, null));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
@ -338,7 +449,11 @@ public abstract class LogHelper {
* @param cause the causing exception (may be null).
*/
public void log(int levels, LogLevel level, String message, Throwable cause) {
log(createLogLine(levels, level, message, cause));
try {
log(createLogLine(levels, level, message, cause));
} catch (Exception e) {
e.printStackTrace();
}
}

View File

@ -53,15 +53,23 @@ public enum LogLevel {
*/
VBOSE;
/** The log level with highest priority */
public static final LogLevel HIGHEST;
/** The log level with lowest priority */
public static final LogLevel LOWEST;
/** The maximum length of a level textual description */
public static final int LENGTH;
static {
int length = 0;
for (LogLevel l : LogLevel.values()) {
length = Math.max(length, l.toString().length());
}
LENGTH = length;
LogLevel[] values = LogLevel.values();
HIGHEST = values[0];
LOWEST = values[values.length - 1];
}
/**

View File

@ -54,12 +54,12 @@ public class LogLevelBufferLogger extends LogHelper {
if (misses > 0) {
if (logs.isEmpty()) {
result.add(new LogLine(level, 0, 0, new TraceException(),
"--- " + misses + " " + level + " lines removed but log is empty! ---",
result.add(new LogLine(level, 0, 0, null,
"===== " + misses + " " + level + " lines removed but log is empty! =====",
null));
} else {
result.add(new LogLine(level, logs.get(0).getLogCount(), 0, new TraceException(),
"--- " + misses + " " + level + " lines removed ---", null));
result.add(new LogLine(level, logs.get(0).getLogCount(), 0, null,
"===== " + misses + " " + level + " lines removed =====", null));
}
}
result.addAll(logs);

View File

@ -31,13 +31,32 @@ public class LogLine implements Comparable<LogLine> {
private volatile String formattedMessage = null;
/**
* Construct a LogLine at the current moment. The next log line count number is selected
* and the current run time set to the timestamp.
*
* @param level the logging level
* @param trace the trace exception for the log line, <code>null</code> permitted
* @param message the log message
* @param cause the causing throwable, <code>null</code> permitted
*/
public LogLine(LogLevel level, TraceException trace, String message, Throwable cause) {
this(level, logCount.getAndIncrement(), System.currentTimeMillis() - startTime, trace, message, cause);
}
public LogLine(LogLevel level, int count, long timestamp,
/**
* Construct a LogLine with all parameters. This should only be used in special conditions,
* for example to insert a log line at a specific point within normal logs.
*
* @param level the logging level
* @param count the log line count number
* @param timestamp the log line timestamp
* @param trace the trace exception for the log line, <code>null</code> permitted
* @param message the log message
* @param cause the causing throwable, <code>null</code> permitted
*/
public LogLine(LogLevel level, int count, long timestamp,
TraceException trace, String message, Throwable cause) {
this.level = level;
this.count = count;
@ -46,57 +65,57 @@ public class LogLine implements Comparable<LogLine> {
this.message = message;
this.cause = cause;
}
/**
* @return the level
*/
public LogLevel getLevel() {
return level;
}
/**
* @return the count
*/
public int getLogCount() {
return count;
}
/**
* @return the timestamp
*/
public long getTimestamp() {
return timestamp;
}
/**
* @return the trace
*/
public TraceException getTrace() {
return trace;
}
/**
* @return the message
*/
public String getMessage() {
return message;
}
/**
* @return the error
*/
public Throwable getCause() {
return cause;
}
/**
@ -109,7 +128,7 @@ public class LogLine implements Comparable<LogLine> {
if (formattedMessage == null) {
String str;
str = String.format("%4d %10.3f %-" + LogLevel.LENGTH + "s %s %s",
count, timestamp/1000.0, (level != null) ? level.toString() : "NULL",
count, timestamp / 1000.0, (level != null) ? level.toString() : "NULL",
(trace != null) ? trace.getMessage() : "(-)",
message);
if (cause != null) {
@ -123,8 +142,8 @@ public class LogLine implements Comparable<LogLine> {
}
return formattedMessage;
}
/**
* Compare against another log line based on the log line count number.
*/

View File

@ -62,6 +62,17 @@ public class TraceException extends Exception {
}
/**
* Construct an exception with the specified message.
*
* @param message the message for the exception.
*/
public TraceException(String message) {
this(0, 0);
this.message = message;
}
/**
* Get the description of the code position as provided in the constructor.
*/

View File

@ -18,23 +18,8 @@ public interface Function {
* @param point the point at which to evaluate the function.
* @return the function value.
* @throws InterruptedException if the thread was interrupted before function evaluation was completed.
* @throws OptimizationException if an error occurs that prevents the optimization
*/
public double evaluate(Point point) throws InterruptedException;
/**
* Return a cached value of the function at the specified point. This allows efficient
* caching of old values even between calls to optimization methods. This method should
* NOT evaluate the function except in special cases (e.g. the point is outside of the
* function domain).
* <p>
* Note that it is allowed to always allowed to return <code>Double.NaN</code>, especially
* for functions that are fast to evaluate.
*
* @param point the point of function evaluation.
* @return the function value, or <code>Double.NaN</code> if the function value has not been
* evaluated at this point.
*/
public double preComputed(Point point);
public double evaluate(Point point) throws InterruptedException, OptimizationException;
}

View File

@ -2,18 +2,38 @@ package net.sf.openrocket.optimization.general;
/**
* A storage of cached values of a function. The purpose of this class is to
* cache function values
* cache function values between optimization runs. Subinterfaces may provide
* additional functionality.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public interface FunctionCache {
/**
* Compute and return the value of the function at the specified point.
*
* @param point the point at which to evaluate.
* @return the value of the function at that point.
*/
public double getValue(Point point);
/**
* Clear the cache.
*/
public void clearCache();
/**
* Return the function that is evaluated by this cache implementation.
*
* @return the function that is being evaluated.
*/
public Function getFunction();
/**
* Set the function that is evaluated by this cache implementation.
*
* @param function the function that is being evaluated.
*/
public void setFunction(Function function);
}

View File

@ -14,8 +14,9 @@ public interface FunctionOptimizer {
*
* @param initial the initial start point of the optimization.
* @param control the optimization control.
* @throws OptimizationException if an error occurs that prevents optimization
*/
public void optimize(Point initial, OptimizationController control);
public void optimize(Point initial, OptimizationController control) throws OptimizationException;
/**

View File

@ -0,0 +1,22 @@
package net.sf.openrocket.optimization.general;
/**
* An exception that prevents optimization from continuing.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class OptimizationException extends Exception {
public OptimizationException(String message) {
super(message);
}
public OptimizationException(Throwable cause) {
super(cause);
}
public OptimizationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -14,11 +14,17 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import net.sf.openrocket.util.BugException;
/**
* An implementation of a ParallelFunctionCache that evaluates function values
* in parallel and caches them. This allows pre-calculating possibly required
* function values beforehand. If values are not required after all, the
* computation can be aborted assuming the function evaluation supports it.
* <p>
* Note that while this class handles threads and abstracts background execution,
* the public methods themselves are NOT thread-safe and should be called from
* only one thread at a time.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
@ -32,13 +38,22 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
private Function function;
/**
* Construct a cache that uses the same number of computational threads as there are
* processors available.
*/
public ParallelExecutorCache() {
this(Runtime.getRuntime().availableProcessors());
}
/**
* Construct a cache that uses the specified number of computational threads for background
* computation. The threads that are created are marked as daemon threads.
*
* @param threadCount the number of threads to use in the executor.
*/
public ParallelExecutorCache(int threadCount) {
executor = new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
this(new ThreadPoolExecutor(threadCount, threadCount, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactory() {
@Override
@ -47,20 +62,22 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
t.setDaemon(true);
return t;
}
});
}));
}
/**
* Construct a cache that uses the specified ExecutorService for managing
* computational threads.
*
* @param executor the executor to use for function evaluations.
*/
public ParallelExecutorCache(ExecutorService executor) {
this.executor = executor;
}
/**
* Queue a list of function evaluations at the specified points.
*
* @param points the points at which to evaluate the function.
*/
@Override
public void compute(Collection<Point> points) {
for (Point p : points) {
compute(p);
@ -68,11 +85,7 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
}
/**
* Queue function evaluation for the specified point.
*
* @param point the point at which to evaluate the function.
*/
@Override
public void compute(Point point) {
if (functionCache.containsKey(point)) {
// Function has already been evaluated at the point
@ -84,13 +97,6 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
return;
}
double value = function.preComputed(point);
if (!Double.isNaN(value)) {
// Function value was in function cache
functionCache.put(point, value);
return;
}
// Submit point for evaluation
FunctionCallable callable = new FunctionCallable(function, point);
Future<Double> future = executor.submit(callable);
@ -98,27 +104,16 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
}
/**
* Wait for a collection of points to be computed. After calling this method
* the function values are available by calling XXX
*
* @param points the points to wait for.
* @throws InterruptedException if this thread was interrupted while waiting.
*/
public void waitFor(Collection<Point> points) throws InterruptedException {
@Override
public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException {
for (Point p : points) {
waitFor(p);
}
}
/**
* Wait for a point to be computed. After calling this method
* the function values are available by calling XXX
*
* @param point the point to wait for.
* @throws InterruptedException if this thread was interrupted while waiting.
*/
public void waitFor(Point point) throws InterruptedException {
@Override
public void waitFor(Point point) throws InterruptedException, OptimizationException {
if (functionCache.containsKey(point)) {
return;
}
@ -132,18 +127,24 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
double value = future.get();
functionCache.put(point, value);
} catch (ExecutionException e) {
throw new IllegalStateException("Function threw exception while processing", e.getCause());
Throwable cause = e.getCause();
if (cause instanceof InterruptedException) {
throw (InterruptedException) cause;
}
if (cause instanceof OptimizationException) {
throw (OptimizationException) cause;
}
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
throw new BugException("Function threw unknown exception while processing", e);
}
}
/**
* Abort the computation of the specified point. If computation has ended,
* the result is stored in the function cache anyway.
*
* @param points the points to abort.
* @return a list of the points that have been computed anyway
*/
@Override
public List<Point> abort(Collection<Point> points) {
List<Point> computed = new ArrayList<Point>(Math.min(points.size(), 10));
@ -157,13 +158,8 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
}
/**
* Abort the computation of the specified point. If computation has ended,
* the result is stored in the function cache anyway.
*
* @param point the point to abort.
* @return <code>true</code> if the point has been computed anyway, <code>false</code> if not.
*/
@Override
public boolean abort(Point point) {
if (functionCache.containsKey(point)) {
return true;
@ -191,17 +187,17 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
}
@Override
public double getValue(Point point) {
Double d = functionCache.get(point);
if (d == null) {
throw new IllegalStateException(point.toString() + " is not in function cache. " +
throw new IllegalStateException(point + " is not in function cache. " +
"functionCache=" + functionCache + " futureMap=" + futureMap);
}
return d;
}
@Override
public Function getFunction() {
return function;
@ -220,6 +216,7 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
functionCache.clear();
}
public ExecutorService getExecutor() {
return executor;
}
@ -239,7 +236,7 @@ public class ParallelExecutorCache implements ParallelFunctionCache {
}
@Override
public Double call() throws InterruptedException {
public Double call() throws InterruptedException, OptimizationException {
return calledFunction.evaluate(point);
}
}

View File

@ -4,7 +4,7 @@ import java.util.Collection;
import java.util.List;
/**
* A FunctionCache that allows queuing points to be computed in the background,
* A FunctionCache that allows scheduling points to be computed in the background,
* waiting for specific points to become computed or aborting the computation of
* points.
*
@ -13,14 +13,17 @@ import java.util.List;
public interface ParallelFunctionCache extends FunctionCache {
/**
* Queue a list of function evaluations at the specified points.
* Schedule a list of function evaluations at the specified points.
* The points are added to the end of the computation queue in the order
* they are returned by the iterator.
*
* @param points the points at which to evaluate the function.
*/
public void compute(Collection<Point> points);
/**
* Queue function evaluation for the specified point.
* Schedule function evaluation for the specified point. The point is
* added to the end of the computation queue.
*
* @param point the point at which to evaluate the function.
*/
@ -28,25 +31,26 @@ public interface ParallelFunctionCache extends FunctionCache {
/**
* Wait for a collection of points to be computed. After calling this method
* the function values are available by calling XXX
* the function values are available by calling {@link #getValue(Point)}.
*
* @param points the points to wait for.
* @throws InterruptedException if this thread was interrupted while waiting.
* @throws InterruptedException if this thread or the computing thread was interrupted while waiting.
*/
public void waitFor(Collection<Point> points) throws InterruptedException;
public void waitFor(Collection<Point> points) throws InterruptedException, OptimizationException;
/**
* Wait for a point to be computed. After calling this method
* the function values are available by calling XXX
* the function value is available by calling {@link #getValue(Point)}.
*
* @param point the point to wait for.
* @throws InterruptedException if this thread was interrupted while waiting.
* @throws InterruptedException if this thread or the computing thread was interrupted while waiting.
* @throws OptimizationException
*/
public void waitFor(Point point) throws InterruptedException;
public void waitFor(Point point) throws InterruptedException, OptimizationException;
/**
* Abort the computation of the specified point. If computation has ended,
* Abort the computation of the specified points. If computation has ended,
* the result is stored in the function cache anyway.
*
* @param points the points to abort.

View File

@ -9,6 +9,7 @@ import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.optimization.general.FunctionCache;
import net.sf.openrocket.optimization.general.FunctionOptimizer;
import net.sf.openrocket.optimization.general.OptimizationController;
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.general.ParallelFunctionCache;
import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.startup.Application;
@ -48,7 +49,7 @@ public class MultidirectionalSearchOptimizer implements FunctionOptimizer, Stati
@Override
public void optimize(Point initial, OptimizationController control) {
public void optimize(Point initial, OptimizationController control) throws OptimizationException {
FunctionCacheComparator comparator = new FunctionCacheComparator(functionExecutor);
final List<Point> pattern = SearchPattern.square(initial.dim());

View File

@ -1,6 +1,7 @@
package net.sf.openrocket.optimization.rocketoptimization;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.optimization.general.OptimizationException;
/**
* A parameter of a rocket or simulation that can be optimized
@ -8,7 +9,7 @@ import net.sf.openrocket.document.Simulation;
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public interface RocketOptimizationParameter {
public interface OptimizableParameter {
/**
* Return the label name for this optimization parameter.
@ -20,10 +21,13 @@ public interface RocketOptimizationParameter {
/**
* Compute the value for this optimization parameter for the simulation.
* The return value can be any double value.
* <p>
* This method can return NaN in case of a problem computing
*
* @param simulation the simulation
* @return the parameter value (any double value)
* @throws OptimizationException if an error occurs preventing the optimization from continuing
*/
public double computeValue(Simulation simulation);
public double computeValue(Simulation simulation) throws OptimizationException;
}

View File

@ -9,7 +9,7 @@ import net.sf.openrocket.document.OpenRocketDocument;
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public interface RocketOptimizationParameterService {
public interface OptimizableParameterService {
/**
* Return all available rocket optimization parameters for this document.
@ -18,6 +18,6 @@ public interface RocketOptimizationParameterService {
* @param document the design document
* @return a collection of the rocket optimization parameters.
*/
public Collection<RocketOptimizationParameter> getParameters(OpenRocketDocument document);
public Collection<OptimizableParameter> getParameters(OpenRocketDocument document);
}

View File

@ -7,6 +7,7 @@ import java.util.concurrent.ConcurrentHashMap;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.optimization.general.Function;
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.rocketcomponent.Rocket;
import net.sf.openrocket.startup.Application;
@ -20,13 +21,16 @@ import net.sf.openrocket.startup.Application;
public class RocketOptimizationFunction implements Function {
private static final LogHelper log = Application.getLogger();
private static final double OUTSIDE_DOMAIN_SCALE = 1.0e200;
/*
* NOTE: This class must be thread-safe!!!
*/
private final Simulation baseSimulation;
private final RocketOptimizationParameter parameter;
private final OptimizableParameter parameter;
private final OptimizationGoal goal;
private final SimulationDomain domain;
private final SimulationModifier[] modifiers;
private final Map<Point, Double> parameterValueCache = new ConcurrentHashMap<Point, Double>();
@ -44,11 +48,12 @@ public class RocketOptimizationFunction implements Function {
* @param goal the goal of the rocket parameter
* @param modifiers the modifiers that modify the simulation
*/
public RocketOptimizationFunction(Simulation baseSimulation, RocketOptimizationParameter parameter,
OptimizationGoal goal, SimulationModifier... modifiers) {
public RocketOptimizationFunction(Simulation baseSimulation, OptimizableParameter parameter,
OptimizationGoal goal, SimulationDomain domain, SimulationModifier... modifiers) {
this.baseSimulation = baseSimulation;
this.parameter = parameter;
this.goal = goal;
this.domain = domain;
this.modifiers = modifiers.clone();
if (modifiers.length == 0) {
throw new IllegalArgumentException("No SimulationModifiers specified");
@ -57,52 +62,71 @@ public class RocketOptimizationFunction implements Function {
@Override
public double evaluate(Point point) throws InterruptedException {
public double evaluate(Point point) throws InterruptedException, OptimizationException {
/*
* parameterValue is the computed parameter value (e.g. altitude)
* goalValue is the value that needs to be minimized
*/
double goalValue, parameterValue;
// Check for precomputed value
double value = preComputed(point);
if (!Double.isNaN(value)) {
return value;
Double d = goalValueCache.get(point);
if (d != null && !Double.isNaN(d)) {
log.verbose("Optimization function value at point " + point + " was found in cache: " + d);
return d;
}
log.verbose("Computing optimization function value at point " + point);
// Create the new simulation based on the point
double[] p = point.asArray();
if (p.length != modifiers.length) {
throw new IllegalArgumentException("Point has length " + p.length + " while function has " +
modifiers.length + " simulation modifiers");
}
Simulation simulation = newSimulationInstance();
Simulation simulation = newSimulationInstance(baseSimulation);
for (int i = 0; i < modifiers.length; i++) {
modifiers[i].modify(simulation, p[i]);
}
// Check whether the point is within the simulation domain
double distance = domain.getDistanceToDomain(simulation);
if (distance > 0 || Double.isNaN(distance)) {
if (Double.isNaN(distance)) {
goalValue = Double.MAX_VALUE;
} else {
goalValue = (distance + 1) * OUTSIDE_DOMAIN_SCALE;
}
parameterValueCache.put(point, Double.NaN);
goalValueCache.put(point, goalValue);
log.verbose("Optimization point is outside of domain, distance=" + distance + " goal function value=" + goalValue);
return goalValue;
}
// Compute the optimization value
value = parameter.computeValue(simulation);
parameterValueCache.put(point, value);
parameterValue = parameter.computeValue(simulation);
parameterValueCache.put(point, parameterValue);
value = goal.getMinimizationParameter(value);
if (Double.isNaN(value)) {
log.warn("Computed value was NaN, baseSimulation=" + baseSimulation + " parameter=" + parameter +
" goal=" + goal + " modifiers=" + Arrays.toString(modifiers) + " simulation=" + simulation);
value = Double.MAX_VALUE;
goalValue = goal.getMinimizationParameter(parameterValue);
if (Double.isNaN(goalValue)) {
log.warn("Computed goal value was NaN, baseSimulation=" + baseSimulation + " parameter=" + parameter +
" goal=" + goal + " modifiers=" + Arrays.toString(modifiers) + " simulation=" + simulation +
" parameter value=" + parameterValue);
goalValue = Double.MAX_VALUE;
}
goalValueCache.put(point, value);
goalValueCache.put(point, goalValue);
return value;
}
@Override
public double preComputed(Point point) {
Double value = goalValueCache.get(point);
if (value != null) {
return value;
}
log.verbose("Parameter value at point " + point + " is " + goalValue + ", goal function value=" + goalValue);
// TODO: : is in domain?
return 0;
return goalValue;
}
/**
* Return the parameter value at a point that has been computed. The purpose is
* to allow retrieving the parameter value corresponding to the found minimum value.
@ -123,13 +147,15 @@ public class RocketOptimizationFunction implements Function {
/**
* Returns a new deep copy of the simulation and rocket. This methods performs
* synchronization on the simulation for thread protection.
* <p>
* Note: This method is package-private for unit testing purposes.
*
* @return
* @return a new deep copy of the simulation and rocket
*/
private Simulation newSimulationInstance() {
Simulation newSimulationInstance(Simulation simulation) {
synchronized (baseSimulation) {
Rocket newRocket = (Rocket) baseSimulation.getRocket().copy();
Simulation newSimulation = baseSimulation.duplicateSimulation(newRocket);
Rocket newRocket = (Rocket) simulation.getRocket().copy();
Simulation newSimulation = simulation.duplicateSimulation(newRocket);
return newSimulation;
}
}

View File

@ -0,0 +1,25 @@
package net.sf.openrocket.optimization.rocketoptimization;
import net.sf.openrocket.document.Simulation;
/**
* An interface defining a function domain which limits allowed function values.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public interface SimulationDomain {
/**
* Return a value determining whether the simulation is within the domain limits
* of an optimization process. If the returned value is negative or zero, the
* simulation is within the domain; if the value is positive, the returned value
* is an indication of how far from the domain the value is; if the returned value
* is NaN, the simulation is outside of the domain.
*
* @param simulation the simulation to check.
* @return a negative value or zero if the simulation is in the domain;
* a positive value or NaN if not.
*/
public double getDistanceToDomain(Simulation simulation);
}

View File

@ -72,7 +72,7 @@ public interface SimulationModifier extends ChangeSource {
/**
* Return the current scaled value. This is normally within the range [0...1], but
* can be outside the range if the current value is outside of the min and max values.
* @return
* @return the current value of this parameter (normally between [0 ... 1])
*/
public double getCurrentScaledValue();

View File

@ -0,0 +1,162 @@
package net.sf.openrocket.optimization.rocketoptimization.modifiers;
import javax.swing.event.ChangeListener;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.optimization.rocketoptimization.SimulationModifier;
import net.sf.openrocket.unit.UnitGroup;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.MathUtil;
import net.sf.openrocket.util.Reflection.Method;
public class GenericModifier implements SimulationModifier {
private final String name;
private final Object relatedObject;
private final UnitGroup unitGroup;
private final double multiplier;
private final Object modifiable;
private final Method getter;
private final Method setter;
private double minValue;
private double maxValue;
public GenericModifier(String modifierName, Object relatedObject, UnitGroup unitGroup, double multiplier,
Object modifiable, String methodName) {
this.name = modifierName;
this.relatedObject = relatedObject;
this.unitGroup = unitGroup;
this.multiplier = multiplier;
this.modifiable = modifiable;
if (MathUtil.equals(multiplier, 0)) {
throw new IllegalArgumentException("multiplier is zero");
}
try {
methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
getter = new Method(modifiable.getClass().getMethod("get" + methodName));
setter = new Method(modifiable.getClass().getMethod("set" + methodName, double.class));
} catch (SecurityException e) {
throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiable.getClass(), e);
} catch (NoSuchMethodException e) {
throw new BugException("Trying to find method get/set" + methodName + " in class " + modifiable.getClass(), e);
}
}
@Override
public String getName() {
return name;
}
@Override
public Object getRelatedObject() {
return relatedObject;
}
@Override
public double getCurrentValue() {
return ((Double) getter.invoke(modifiable)) * multiplier;
}
@Override
public double getCurrentScaledValue() {
double value = getCurrentValue();
return toScaledValue(value);
}
@Override
public void modify(Simulation simulation, double scaledValue) {
double siValue = toBaseValue(scaledValue) / multiplier;
setter.invoke(modifiable, siValue);
}
/**
* Returns the scaled value (normally within [0...1]).
*/
private double toScaledValue(double value) {
if (MathUtil.equals(minValue, maxValue)) {
if (value > maxValue)
return 1.0;
if (value < minValue)
return 0.0;
return 0.5;
}
return MathUtil.map(value, minValue, maxValue, 0.0, 1.0);
}
/**
* Returns the base value (in SI units).
*/
private double toBaseValue(double value) {
return MathUtil.map(value, 0.0, 1.0, minValue, maxValue);
}
@Override
public double getMinValue() {
return minValue;
}
@Override
public void setMinValue(double value) {
if (MathUtil.equals(minValue, value))
return;
this.minValue = value;
if (maxValue < minValue)
maxValue = minValue;
fireChangeEvent();
}
@Override
public double getMaxValue() {
return maxValue;
}
@Override
public void setMaxValue(double value) {
if (MathUtil.equals(maxValue, value))
return;
this.maxValue = value;
if (minValue > maxValue)
minValue = maxValue;
fireChangeEvent();
}
@Override
public UnitGroup getUnitGroup() {
return unitGroup;
}
@Override
public void addChangeListener(ChangeListener listener) {
// TODO Auto-generated method stub
}
@Override
public void removeChangeListener(ChangeListener listener) {
// TODO Auto-generated method stub
}
private void fireChangeEvent() {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,32 @@
package net.sf.openrocket.optimization.rocketoptimization.parameters;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.rocketoptimization.OptimizableParameter;
import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.system.ApogeeEndListener;
/**
* An optimization parameter that computes the maximum altitude of a simulated flight.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class MaximumAltitudeParameter implements OptimizableParameter {
@Override
public String getName() {
return "Maximum altitude";
}
@Override
public double computeValue(Simulation simulation) throws OptimizationException {
try {
simulation.simulate(new ApogeeEndListener());
return simulation.getSimulatedData().getBranch(0).getMaximum(FlightDataType.TYPE_ALTITUDE);
} catch (SimulationException e) {
throw new OptimizationException(e);
}
}
}

View File

@ -12,7 +12,10 @@ import java.util.UUID;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;
import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.Chars;
import net.sf.openrocket.util.Coordinate;
import net.sf.openrocket.util.MathUtil;
@ -30,9 +33,9 @@ import net.sf.openrocket.util.UniqueID;
*/
public class Rocket extends RocketComponent {
public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
private static final LogHelper log = Application.getLogger();
private static final boolean DEBUG_LISTENERS = false;
public static final double DEFAULT_REFERENCE_LENGTH = 0.01;
/**
@ -343,17 +346,15 @@ public class Rocket extends RocketComponent {
public void addComponentChangeListener(ComponentChangeListener l) {
checkState();
listenerList.add(ComponentChangeListener.class, l);
if (DEBUG_LISTENERS)
System.out.println(this + ": Added listner (now " + listenerList.getListenerCount() +
" listeners): " + l);
log.verbose("Added ComponentChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
@Override
public void removeComponentChangeListener(ComponentChangeListener l) {
listenerList.remove(ComponentChangeListener.class, l);
if (DEBUG_LISTENERS)
System.out.println(this + ": Removed listner (now " + listenerList.getListenerCount() +
" listeners): " + l);
log.verbose("Removed ComponentChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
@ -361,17 +362,15 @@ public class Rocket extends RocketComponent {
public void addChangeListener(ChangeListener l) {
checkState();
listenerList.add(ChangeListener.class, l);
if (DEBUG_LISTENERS)
System.out.println(this + ": Added listner (now " + listenerList.getListenerCount() +
" listeners): " + l);
log.verbose("Added ChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
@Override
public void removeChangeListener(ChangeListener l) {
listenerList.remove(ChangeListener.class, l);
if (DEBUG_LISTENERS)
System.out.println(this + ": Removed listner (now " + listenerList.getListenerCount() +
" listeners): " + l);
log.verbose("Removed ChangeListener " + l + ", current number of listeners is " +
listenerList.getListenerCount());
}
@ -392,15 +391,15 @@ public class Rocket extends RocketComponent {
functionalModID = modID;
}
if (DEBUG_LISTENERS)
System.out.println("FIRING " + e);
// Check whether frozen
if (freezeList != null) {
log.debug("Rocket is in frozen state, adding event " + e + " info freeze list");
freezeList.add(e);
return;
}
log.debug("Firing rocket change event " + e);
// Notify all components first
Iterator<RocketComponent> iterator = this.deepIterator(true);
while (iterator.hasNext()) {
@ -441,6 +440,10 @@ public class Rocket extends RocketComponent {
checkState();
if (freezeList == null) {
freezeList = new LinkedList<ComponentChangeEvent>();
log.debug("Freezing Rocket");
} else {
ExceptionHandler.handleErrorCondition("Attempting to freeze Rocket when it is already frozen, " +
"freezeList=" + freezeList);
}
}
@ -453,13 +456,18 @@ public class Rocket extends RocketComponent {
*/
public void thaw() {
checkState();
if (freezeList == null)
if (freezeList == null) {
ExceptionHandler.handleErrorCondition("Attempting to thaw Rocket when it is not frozen");
return;
}
if (freezeList.size() == 0) {
log.warn("Thawing rocket with no changes made");
freezeList = null;
return;
}
log.debug("Thawing rocket, freezeList=" + freezeList);
int type = 0;
Object c = null;
for (ComponentChangeEvent e : freezeList) {

View File

@ -12,7 +12,9 @@ import java.util.Stack;
import javax.swing.event.ChangeListener;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.logging.TraceException;
import net.sf.openrocket.startup.Application;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.Coordinate;
@ -23,6 +25,7 @@ import net.sf.openrocket.util.UniqueID;
public abstract class RocketComponent implements ChangeSource, Cloneable,
Iterable<RocketComponent> {
private static final LogHelper log = Application.getLogger();
/*
* Text is suitable to the form
@ -1304,6 +1307,7 @@ public abstract class RocketComponent implements ChangeSource, Cloneable,
checkState();
if (parent == null) {
/* Ignore if root invalid. */
log.debug("Attempted firing event " + e + " with root " + this.getComponentName() + ", ignoring event");
return;
}
getRoot().fireComponentChangeEvent(e);

View File

@ -41,6 +41,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
private SimulationStatus status;
@Override
public FlightData simulate(SimulationConditions simulationConditions) throws SimulationException {
Set<MotorId> motorBurntOut = new HashSet<MotorId>();
@ -409,7 +410,7 @@ public class BasicEventSimulationEngine implements SimulationEngine {
case STAGE_SEPARATION: {
// TODO: HIGH: Store lower stages to be simulated later
RocketComponent stage = (RocketComponent) event.getSource();
RocketComponent stage = event.getSource();
int n = stage.getStageNumber();
status.getConfiguration().setToStage(n);
status.getFlightData().addEvent(event);
@ -542,6 +543,14 @@ public class BasicEventSimulationEngine implements SimulationEngine {
d += status.getEffectiveLaunchRodLength();
if (Double.isNaN(d) || b) {
log.error("Simulation resulted in NaN value:" +
" simulationTime=" + status.getSimulationTime() +
" previousTimeStep=" + status.getPreviousTimeStep() +
" rocketPosition=" + status.getRocketPosition() +
" rocketVelocity=" + status.getRocketVelocity() +
" rocketOrientationQuaternion=" + status.getRocketOrientationQuaternion() +
" rocketRotationVelocity=" + status.getRocketRotationVelocity() +
" effectiveLaunchRodLength=" + status.getEffectiveLaunchRodLength());
throw new SimulationException("Simulation resulted in not-a-number (NaN) value, please report a bug.");
}
}

View File

@ -62,6 +62,9 @@ public class Startup {
public static void main(final String[] args) throws Exception {
// Check for "openrocket.debug" property before anything else
checkDebugStatus();
// Initialize logging first so we can use it
initializeLogging();
@ -91,6 +94,18 @@ public class Startup {
private static void checkDebugStatus() {
if (System.getProperty("openrocket.debug") != null) {
System.setProperty("openrocket.log.stdout", "VBOSE");
System.setProperty("openrocket.log.tracelevel", "VBOSE");
System.setProperty("openrocket.debug.menu", "true");
System.setProperty("openrocket.debug.motordigest", "true");
}
}
private static void runMain(String[] args) {
// Initialize the splash screen with version info

View File

@ -0,0 +1,26 @@
package net.sf.openrocket.util;
/**
* An exception that indicates a concurrency bug in the software.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class ConcurrencyException extends FatalException {
public ConcurrencyException() {
super();
}
public ConcurrencyException(String message, Throwable cause) {
super(message, cause);
}
public ConcurrencyException(String message) {
super(message);
}
public ConcurrencyException(Throwable cause) {
super(cause);
}
}

View File

@ -128,6 +128,7 @@ public class GUIUtil {
*/
public static void installEscapeCloseOperation(final JDialog dialog) {
Action dispatchClosing = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent event) {
log.user("Closing dialog " + dialog);
dialog.dispatchEvent(new WindowEvent(dialog, WindowEvent.WINDOW_CLOSING));
@ -194,6 +195,7 @@ public class GUIUtil {
@Override
public void windowClosed(WindowEvent e) {
setNullModels(window);
MemoryManagement.collectable(window);
}
});
}
@ -341,7 +343,7 @@ public class GUIUtil {
JTree tree = (JTree) c;
tree.setModel(new DefaultTreeModel(new TreeNode() {
@SuppressWarnings("unchecked")
@SuppressWarnings("rawtypes")
@Override
public Enumeration children() {
return new Vector().elements();

View File

@ -0,0 +1,137 @@
package net.sf.openrocket.util;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
/**
* A class that performs certain memory-management operations for debugging purposes.
* For example, complex objects that are being discarded and that should be garbage-collectable
* (such as dialog windows) should be registered for monitoring by calling
* {@link #collectable(Object)}. This will allow monitoring whether the object really is
* garbage-collected or whether it is retained in memory due to a memory leak.
* Only complex objects should be registered due to the overhead of the monitoring.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public final class MemoryManagement {
private static final LogHelper log = Application.getLogger();
/** Purge cleared references every this many calls to {@link #collectable(Object)} */
private static final int PURGE_CALL_COUNT = 100;
/**
* Storage of the objects. This is basically a mapping from the objects (using weak references)
* to
*/
private static List<MemoryData> objects = new LinkedList<MemoryData>();
private static int callCount = 0;
private MemoryManagement() {
}
/**
* Mark an object that should be garbage-collectable by the GC. This class will monitor
* whether the object actually gets garbage-collected or not by holding a weak reference
* to the object.
*
* @param o the object to monitor.
*/
public static synchronized void collectable(Object o) {
if (o == null) {
throw new IllegalArgumentException("object is null");
}
log.debug("Adding object into collectable list: " + o);
objects.add(new MemoryData(o));
callCount++;
if (callCount % PURGE_CALL_COUNT == 0) {
purge();
}
}
/**
* Return the number of times {@link #collectable(Object)} has been called.
* @return the number of times {@link #collectable(Object)} has been called.
*/
public static synchronized int getCallCount() {
return callCount;
}
/**
* Return a list of MemoryData objects corresponding to the objects that have been
* registered by {@link #collectable(Object)} and have not been garbage-collected properly.
* This method first calls <code>System.gc()</code> multiple times to attempt to
* force any remaining garbage collection.
*
* @return a list of MemoryData objects for objects that have not yet been garbage-collected.
*/
public static synchronized ArrayList<MemoryData> getRemainingObjects() {
for (int i = 0; i < 5; i++) {
System.runFinalization();
System.gc();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
purge();
return new ArrayList<MemoryData>(objects);
}
/**
* Purge all cleared references from the object list.
*/
private static void purge() {
int origCount = objects.size();
Iterator<MemoryData> iterator = objects.iterator();
while (iterator.hasNext()) {
MemoryData data = iterator.next();
if (data.getReference().get() == null) {
iterator.remove();
}
}
log.debug(objects.size() + " of " + origCount + " objects remaining in discarded objects list after purge.");
}
/**
* A value object class containing data of a discarded object reference.
*/
public static final class MemoryData {
private final WeakReference<Object> reference;
private final long registrationTime;
private MemoryData(Object object) {
this.reference = new WeakReference<Object>(object);
this.registrationTime = System.currentTimeMillis();
}
/**
* Return the weak reference to the discarded object.
*/
public WeakReference<Object> getReference() {
return reference;
}
/**
* Return the time when the object was discarded.
* @return a millisecond timestamp of when the object was discarded.
*/
public long getRegistrationTime() {
return registrationTime;
}
}
}

View File

@ -0,0 +1,171 @@
package net.sf.openrocket.util;
import java.util.LinkedList;
import net.sf.openrocket.gui.main.ExceptionHandler;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.startup.Application;
/**
* A mutex that can be used for verifying thread safety. This class cannot be
* used to perform synchronization, only to detect concurrency issues. This
* class can be used by the main methods of non-thread-safe classes to ensure
* the class is not wrongly used from multiple threads concurrently.
* <p>
* This mutex is not reentrant even for the same thread that has locked it.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class SafetyMutex {
private static final LogHelper log = Application.getLogger();
// Package-private for unit testing
static volatile boolean errorReported = false;
// lockingThread is set when this mutex is locked.
Thread lockingThread = null;
// Stack of places that have locked this mutex
final LinkedList<String> locations = new LinkedList<String>();
/**
* Verify that this mutex is unlocked, but don't lock it. This has the same effect
* as <code>mutex.lock(); mutex.unlock();</code> and is useful for methods that return
* immediately (e.g. getters).
*
* @throws ConcurrencyException if this mutex is already locked.
*/
public synchronized void verify() {
checkState(true);
if (lockingThread != null && lockingThread != Thread.currentThread()) {
error("Mutex is already locked", true);
}
}
/**
* Lock this mutex. If this mutex is already locked an error is raised and
* a ConcurrencyException is thrown. The location parameter is used to distinguish
* the locking location, and it should be e.g. the method name.
*
* @param location a string describing the location where this mutex was locked (cannot be null).
*
* @throws ConcurrencyException if this mutex is already locked.
*/
public synchronized void lock(String location) {
if (location == null) {
throw new IllegalArgumentException("location is null");
}
checkState(true);
Thread currentThread = Thread.currentThread();
if (lockingThread != null && lockingThread != currentThread) {
error("Mutex is already locked", true);
}
lockingThread = currentThread;
locations.push(location);
}
/**
* Unlock this mutex. If this mutex is not locked at the position of the parameter
* or was locked by another thread than the current thread an error is raised,
* but an exception is not thrown.
* <p>
* This method is guaranteed never to throw an exception, so it can safely be used in finally blocks.
*
* @param location a location string matching that which locked the mutex
* @return whether the unlocking was successful (this normally doesn't need to be checked)
*/
public synchronized boolean unlock(String location) {
try {
if (location == null) {
ExceptionHandler.handleErrorCondition("location is null");
location = "";
}
checkState(false);
// Check that the mutex is locked
if (lockingThread == null) {
error("Mutex was not locked", false);
return false;
}
// Check that the mutex is locked by the current thread
if (lockingThread != Thread.currentThread()) {
error("Mutex is being unlocked from differerent thread than where it was locked", false);
return false;
}
// Check that the unlock location is correct
String lastLocation = locations.pop();
if (!location.equals(lastLocation)) {
locations.push(lastLocation);
error("Mutex unlocking location does not match locking location, location=" + location, false);
return false;
}
// Unlock the mutex if the last one
if (locations.isEmpty()) {
lockingThread = null;
}
return true;
} catch (Exception e) {
ExceptionHandler.handleErrorCondition("An exception occurred while unlocking a mutex, " +
"locking thread=" + lockingThread + " locations=" + locations, e);
return false;
}
}
/**
* Check that the internal state of the mutex (lockingThread vs. locations) is correct.
*/
private void checkState(boolean throwException) {
/*
* Disallowed states:
* lockingThread == null && !locations.isEmpty()
* lockingThread != null && locations.isEmpty()
*/
if ((lockingThread == null) ^ (locations.isEmpty())) {
// Clear the mutex only after error() has executed (and possibly thrown an exception)
try {
error("Mutex data inconsistency occurred - unlocking mutex", throwException);
} finally {
lockingThread = null;
locations.clear();
}
}
}
/**
* Raise an error. The first occurrence is passed directly to the exception handler,
* later errors are simply logged.
*/
private void error(String message, boolean throwException) {
message = message +
", current thread = " + Thread.currentThread() +
", locking thread=" + lockingThread +
", locking locations=" + locations;
ConcurrencyException ex = new ConcurrencyException(message);
if (!errorReported) {
errorReported = true;
ExceptionHandler.handleErrorCondition(ex);
} else {
log.error(message, ex);
}
if (throwException) {
throw ex;
}
}
}

View File

@ -3,6 +3,7 @@ package net.sf.openrocket.utils;
import net.sf.openrocket.optimization.general.Function;
import net.sf.openrocket.optimization.general.FunctionOptimizer;
import net.sf.openrocket.optimization.general.OptimizationController;
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.general.ParallelExecutorCache;
import net.sf.openrocket.optimization.general.ParallelFunctionCache;
import net.sf.openrocket.optimization.general.Point;
@ -22,7 +23,7 @@ public class TestFunctionOptimizer {
private void go(final ParallelFunctionCache functionCache,
final FunctionOptimizer optimizer, final Point optimum, final int maxSteps) {
final FunctionOptimizer optimizer, final Point optimum, final int maxSteps) throws OptimizationException {
Function function = new Function() {
@Override
@ -35,15 +36,6 @@ public class TestFunctionOptimizer {
return Double.NaN;
}
}
@Override
public double preComputed(Point p) {
for (double d : p.asArray()) {
if (d < 0 || d > 1)
return Double.MAX_VALUE;
}
return Double.NaN;
}
};
OptimizationController control = new OptimizationController() {
@ -83,7 +75,7 @@ public class TestFunctionOptimizer {
}
public static void main(String[] args) throws InterruptedException {
public static void main(String[] args) throws InterruptedException, OptimizationException {
System.err.println("Number of processors: " + Runtime.getRuntime().availableProcessors());

View File

@ -10,6 +10,7 @@ import java.util.concurrent.TimeUnit;
import net.sf.openrocket.optimization.general.Function;
import net.sf.openrocket.optimization.general.FunctionOptimizer;
import net.sf.openrocket.optimization.general.OptimizationController;
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.general.ParallelExecutorCache;
import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.optimization.general.multidim.MultidirectionalSearchOptimizer;
@ -26,7 +27,7 @@ public class TestFunctionOptimizerLoop {
private void go(final FunctionOptimizer optimizer, final Point optimum, final int maxSteps, ExecutorService executor) {
private void go(final FunctionOptimizer optimizer, final Point optimum, final int maxSteps, ExecutorService executor) throws OptimizationException {
Function function = new Function() {
@Override
@ -34,15 +35,6 @@ public class TestFunctionOptimizerLoop {
evaluations++;
return p.sub(optimum).length2();
}
@Override
public double preComputed(Point p) {
for (double d : p.asArray()) {
if (d < 0 || d > 1)
return Double.MAX_VALUE;
}
return Double.NaN;
}
};
OptimizationController control = new OptimizationController() {
@ -67,7 +59,7 @@ public class TestFunctionOptimizerLoop {
}
public static void main(String[] args) {
public static void main(String[] args) throws OptimizationException {
System.err.println("PRECISION = " + PRECISION);

View File

@ -0,0 +1,214 @@
package net.sf.openrocket.optimization.rocketoptimization;
import static org.junit.Assert.*;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.optimization.general.OptimizationException;
import net.sf.openrocket.optimization.general.Point;
import net.sf.openrocket.rocketcomponent.Rocket;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.auto.Mock;
import org.jmock.integration.junit4.JMock;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(JMock.class)
public class TestRocketOptimizationFunction {
Mockery context = new JUnit4Mockery();
@Mock
OptimizableParameter parameter;
@Mock
OptimizationGoal goal;
@Mock
SimulationDomain domain;
@Mock
SimulationModifier modifier1;
@Mock
SimulationModifier modifier2;
@Test
public void testNormalEvaluation() throws InterruptedException, OptimizationException {
final Rocket rocket = new Rocket();
final Simulation simulation = new Simulation(rocket);
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = -0.43;
final double pvalue = 9.81;
final double gvalue = 8.81;
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(gvalue));
}});
// @formatter:on
RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
parameter, goal, domain, modifier1, modifier2) {
@Override
Simulation newSimulationInstance(Simulation sim) {
return sim;
}
};
assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
double value = function.evaluate(new Point(p1, p2));
assertEquals(gvalue, value, 0);
assertEquals(pvalue, function.getComputedParameterValue(new Point(p1, p2)), 0);
// Re-evaluate the point to verify parameter is not recomputed
value = function.evaluate(new Point(p1, p2));
assertEquals(gvalue, value, 0);
}
@Test
public void testNaNValue() throws InterruptedException, OptimizationException {
final Rocket rocket = new Rocket();
final Simulation simulation = new Simulation(rocket);
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = -0.43;
final double pvalue = 9.81;
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
oneOf(parameter).computeValue(simulation); will(returnValue(pvalue));
oneOf(goal).getMinimizationParameter(pvalue); will(returnValue(Double.NaN));
}});
// @formatter:on
RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
parameter, goal, domain, modifier1, modifier2) {
@Override
Simulation newSimulationInstance(Simulation sim) {
return sim;
}
};
assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
double value = function.evaluate(new Point(p1, p2));
assertEquals(Double.MAX_VALUE, value, 0);
assertEquals(pvalue, function.getComputedParameterValue(new Point(p1, p2)), 0);
value = function.evaluate(new Point(p1, p2));
assertEquals(Double.MAX_VALUE, value, 0);
}
@Test
public void testOutsideDomain() throws InterruptedException, OptimizationException {
final Rocket rocket = new Rocket();
final Simulation simulation = new Simulation(rocket);
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = 0.98;
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
}});
// @formatter:on
RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
parameter, goal, domain, modifier1, modifier2) {
@Override
Simulation newSimulationInstance(Simulation sim) {
return sim;
}
};
assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
double value = function.evaluate(new Point(p1, p2));
assertTrue(value > 1e100);
assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
value = function.evaluate(new Point(p1, p2));
assertTrue(value > 1e100);
}
@Test
public void testOutsideDomain2() throws InterruptedException, OptimizationException {
final Rocket rocket = new Rocket();
final Simulation simulation = new Simulation(rocket);
final double p1 = 0.4;
final double p2 = 0.7;
final double ddist = Double.NaN;
// @formatter:off
context.checking(new Expectations() {{
oneOf(modifier1).modify(simulation, p1);
oneOf(modifier2).modify(simulation, p2);
oneOf(domain).getDistanceToDomain(simulation); will(returnValue(ddist));
}});
// @formatter:on
RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
parameter, goal, domain, modifier1, modifier2) {
@Override
Simulation newSimulationInstance(Simulation sim) {
return sim;
}
};
assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
double value = function.evaluate(new Point(p1, p2));
assertEquals(Double.MAX_VALUE, value, 0);
assertEquals(Double.NaN, function.getComputedParameterValue(new Point(p1, p2)), 0);
value = function.evaluate(new Point(p1, p2));
assertEquals(Double.MAX_VALUE, value, 0);
}
@Test
public void testNewSimulationInstance() {
final Rocket rocket = new Rocket();
rocket.setName("Foobar");
final Simulation simulation = new Simulation(rocket);
simulation.setName("MySim");
RocketOptimizationFunction function = new RocketOptimizationFunction(simulation,
parameter, goal, domain, modifier1, modifier2);
Simulation sim = function.newSimulationInstance(simulation);
assertFalse(simulation == sim);
assertEquals("MySim", sim.getName());
assertFalse(rocket == sim.getRocket());
assertEquals("Foobar", sim.getRocket().getName());
}
}

View File

@ -0,0 +1,156 @@
package net.sf.openrocket.util;
import static org.junit.Assert.*;
import org.junit.Test;
public class TestMutex {
@Test
public void testSingleLocking() {
SafetyMutex m = new SafetyMutex();
// Test single locking
assertNull(m.lockingThread);
m.verify();
m.lock("here");
assertNotNull(m.lockingThread);
assertTrue(m.unlock("here"));
}
@Test
public void testDoubleLocking() {
SafetyMutex m = new SafetyMutex();
// Test double locking
m.verify();
m.lock("foobar");
m.verify();
m.lock("bazqux");
m.verify();
assertTrue(m.unlock("bazqux"));
m.verify();
assertTrue(m.unlock("foobar"));
m.verify();
}
@Test
public void testDoubleUnlocking() {
SafetyMutex m = new SafetyMutex();
// Mark error reported to not init exception handler
SafetyMutex.errorReported = true;
m.lock("here");
assertTrue(m.unlock("here"));
assertFalse(m.unlock("here"));
}
private volatile int testState = 0;
private volatile String failure = null;
@Test(timeout = 1000)
public void testThreadingErrors() {
final SafetyMutex m = new SafetyMutex();
// Initialize and start the thread
Thread thread = new Thread() {
@Override
public void run() {
try {
// Test locking a locked mutex
waitFor(1);
try {
m.lock("in thread one");
failure = "Succeeded in locking a mutex locked by a different thread";
return;
} catch (ConcurrencyException e) {
// OK
}
// Test unlocking a mutex locked by a different thread
if (m.unlock("in thread two")) {
failure = "Succeeded in unlocking a mutex locked by a different thread";
return;
}
// Test verifying a locked mutex that already has an error
try {
m.verify();
failure = "Succeeded in verifying a mutex locked by a different thread";
return;
} catch (ConcurrencyException e) {
// OK
}
// Test locking a mutex after it's been unlocked
testState = 2;
waitFor(3);
m.lock("in thread three");
m.verify();
// Wait for other side to test
testState = 4;
waitFor(5);
// Exit code
testState = 6;
} catch (Exception e) {
failure = "Exception occurred in thread: " + e;
return;
}
}
};
thread.setDaemon(true);
thread.start();
m.lock("one");
testState = 1;
waitFor(2);
assertNull("Thread error: " + failure, failure);
m.verify();
m.unlock("one");
testState = 3;
waitFor(4);
assertNull("Thread error: " + failure, failure);
try {
m.lock("two");
fail("Succeeded in locking a locked mutex in main thread");
} catch (ConcurrencyException e) {
// OK
}
// Test unlocking a mutex locked by a different thread
assertFalse(m.unlock("here"));
try {
m.verify();
fail("Succeeded in verifying a locked mutex in main thread");
} catch (ConcurrencyException e) {
// OK
}
testState = 5;
waitFor(6);
assertNull("Thread error: " + failure, failure);
}
private void waitFor(int state) {
while (testState != state && failure == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
}
}