Initial implementation of scripting extension

This commit is contained in:
Sampo Niskanen 2014-12-27 19:18:53 +02:00
parent 78cdd77d78
commit a2c0af3b7f
6 changed files with 365 additions and 0 deletions

View File

@ -405,6 +405,10 @@ SimulationExtension.javacode.name.none = none
SimulationExtension.javacode.desc = Add a custom SimulationListener to the simulation
SimulationExtension.javacode.className = Fully-qualified Java class name:
SimulationExtension.scripting.name = {language} script
SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts.
SimulationExtension.scripting.script.label = JavaScript code:
SimulationEditDialog.btn.plot = Plot
SimulationEditDialog.btn.export = Export
SimulationEditDialog.btn.edit = Edit

View File

@ -0,0 +1,72 @@
package net.sf.openrocket.simulation.extension.impl;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener;
public class ScriptingExtension extends AbstractSimulationExtension {
private static final String JS = "JavaScript";
public ScriptingExtension() {
setLanguage(JS);
}
@Override
public String getName() {
String name = trans.get("SimulationExtension.scripting.name");
name = L10N.replace(name, "{language}", getLanguage());
return name;
}
@Override
public String getDescription() {
return trans.get("SimulationExtension.scripting.desc");
}
@Override
public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(getListener());
}
public String getScript() {
return config.getString("script", "");
}
public void setScript(String script) {
config.put("script", script);
}
public String getLanguage() {
// TODO: Support other languages
return JS;
}
public void setLanguage(String language) {
config.put("language", language);
}
SimulationListener getListener() throws SimulationException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
engine.eval(getScript());
} catch (ScriptException e) {
throw new SimulationException("Invalid script: " + e.getMessage());
}
// TODO: Check for implementation first
return new ScriptingSimulationListener((Invocable) engine);
}
}

View File

@ -0,0 +1,13 @@
package net.sf.openrocket.simulation.extension.impl;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtensionProvider;
@Plugin
public class ScriptingProvider extends AbstractSimulationExtensionProvider {
public ScriptingProvider() {
super(ScriptingExtension.class, "User code", "Scripts");
}
}

View File

@ -0,0 +1,217 @@
package net.sf.openrocket.simulation.extension.impl;
import java.util.HashSet;
import java.util.Set;
import javax.script.Invocable;
import javax.script.ScriptException;
import net.sf.openrocket.aerodynamics.AerodynamicForces;
import net.sf.openrocket.aerodynamics.FlightConditions;
import net.sf.openrocket.models.atmosphere.AtmosphericConditions;
import net.sf.openrocket.motor.MotorId;
import net.sf.openrocket.motor.MotorInstance;
import net.sf.openrocket.rocketcomponent.MotorMount;
import net.sf.openrocket.rocketcomponent.RecoveryDevice;
import net.sf.openrocket.simulation.AccelerationData;
import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.listeners.SimulationComputationListener;
import net.sf.openrocket.simulation.listeners.SimulationEventListener;
import net.sf.openrocket.simulation.listeners.SimulationListener;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.Coordinate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ScriptingSimulationListener implements SimulationListener, SimulationComputationListener, SimulationEventListener, Cloneable {
private final static Logger logger = LoggerFactory.getLogger(ScriptingSimulationListener.class);
/*
* NOTE: This class is used instead of using the scripting interface API
* so that unimplemented script methods are not called unnecessarily.
*/
private Invocable invocable;
private Set<String> missing = new HashSet<String>();
public ScriptingSimulationListener(Invocable invocable) {
this.invocable = invocable;
}
@Override
public boolean isSystemListener() {
return false;
}
@Override
public SimulationListener clone() {
try {
ScriptingSimulationListener clone = (ScriptingSimulationListener) super.clone();
clone.missing = new HashSet<String>(missing);
return clone;
} catch (CloneNotSupportedException e) {
throw new BugException(e);
}
}
//// SimulationListener ////
@Override
public void startSimulation(SimulationStatus status) throws SimulationException {
invoke(null, "startSimulation", status);
}
@Override
public void endSimulation(SimulationStatus status, SimulationException exception) {
try {
invoke(null, "endSimulation", status, exception);
} catch (SimulationException e) {
}
}
@Override
public boolean preStep(SimulationStatus status) throws SimulationException {
return invoke(true, "preStep", status);
}
@Override
public void postStep(SimulationStatus status) throws SimulationException {
invoke(null, "postStep", status);
}
//// SimulationEventListener ////
@Override
public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
return invoke(true, "addFlightEvent", status, event);
}
@Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
return invoke(true, "handleFlightEvent", status, event);
}
@Override
public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException {
return invoke(true, "motorIgnition", status, motorId, mount, instance);
}
@Override
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
return invoke(true, "recoveryDeviceDeployment", status, recoveryDevice);
}
//// SimulationComputationListener ////
@Override
public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException {
return invoke(null, "preAccelerationCalculation", status);
}
@Override
public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException {
return invoke(null, "preAerodynamicCalculation", status);
}
@Override
public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException {
return invoke(null, "preAtmosphericModel", status);
}
@Override
public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException {
return invoke(null, "preFlightConditions", status);
}
@Override
public double preGravityModel(SimulationStatus status) throws SimulationException {
return invoke(Double.NaN, "preGravityModel", status);
}
@Override
public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
return invoke(null, "preMassCalculation", status);
}
@Override
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException {
return invoke(Double.NaN, "preSimpleThrustCalculation", status);
}
@Override
public Coordinate preWindModel(SimulationStatus status) throws SimulationException {
return invoke(null, "preWindModel", status);
}
@Override
public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException {
return invoke(null, "postAccelerationCalculation", status, acceleration);
}
@Override
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException {
return invoke(null, "postAerodynamicCalculation", status, forces);
}
@Override
public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException {
return invoke(null, "postAtmosphericModel", status, atmosphericConditions);
}
@Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
return invoke(null, "postFlightConditions", status, flightConditions);
}
@Override
public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException {
return invoke(Double.NaN, "postGravityModel", status, gravity);
}
@Override
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
return invoke(null, "postMassCalculation", status, massData);
}
@Override
public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException {
return invoke(Double.NaN, "postSimpleThrustCalculation", status, thrust);
}
@Override
public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException {
return invoke(null, "postWindModel", status, wind);
}
@SuppressWarnings("unchecked")
private <T> T invoke(T def, String method, Object... args) throws SimulationException {
try {
if (!missing.contains(method)) {
return (T) invocable.invokeFunction(method, args);
}
} catch (NoSuchMethodException e) {
missing.add(method);
// fall-through
} catch (ScriptException e) {
logger.warn("Script exception in " + method + ": " + e, e);
throw new SimulationException("Script failed: " + e.getMessage());
}
return def;
}
}

View File

@ -4,6 +4,7 @@ import net.sf.openrocket.database.ComponentPresetDao;
import net.sf.openrocket.database.motor.MotorDatabase;
import net.sf.openrocket.database.motor.ThrustCurveMotorSetDatabase;
import net.sf.openrocket.l10n.ClassBasedTranslator;
import net.sf.openrocket.l10n.DebugTranslator;
import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
import net.sf.openrocket.l10n.Translator;
@ -33,6 +34,10 @@ public final class Application {
}
private static Translator getBaseTranslator() {
if (injector == null) {
// Occurs in some unit tests
return new DebugTranslator(null);
}
return injector.getInstance(Translator.class);
}

View File

@ -0,0 +1,54 @@
package net.sf.openrocket.simulation.extension.impl;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.gui.util.GUIUtil;
import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
@Plugin
public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfigurator<ScriptingExtension> {
protected ScriptingConfigurator() {
super(ScriptingExtension.class);
}
@Override
protected JComponent getConfigurationComponent(final ScriptingExtension extension, Simulation simulation, JPanel panel) {
panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.script.label"), Style.BOLD), "wrap");
final JTextArea text = new JTextArea(extension.getScript(), 20, 60);
text.setLineWrap(true);
text.setWrapStyleWord(true);
text.setEditable(true);
GUIUtil.setTabToFocusing(text);
text.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent event) {
}
@Override
public void focusLost(FocusEvent event) {
String str = text.getText();
if (!extension.getScript().equals(str)) {
extension.setScript(str);
}
}
});
panel.add(new JScrollPane(text), "grow");
return panel;
}
}