Difference between revisions of "Simulation Extensions"
JoePfeiffer (talk | contribs) |
JoePfeiffer (talk | contribs) |
||
Line 4: | Line 4: | ||
== Adding an Existing Extension to a Simulation == | == Adding an Existing Extension to a Simulation == | ||
+ | |||
Extensions are added to a simulation through a menu in the "Simulation Options" tab. | Extensions are added to a simulation through a menu in the "Simulation Options" tab. | ||
# Open a .ork file and go to the '''Flight Simulations''' tab | # Open a .ork file and go to the '''Flight Simulations''' tab | ||
Line 21: | Line 22: | ||
[[File:Air-start-pane.png]] | [[File:Air-start-pane.png]] | ||
− | == | + | == Some Preliminary Concepts == |
− | The | + | |
+ | Before we can discuss writing an extension, we need to briefly discuss some of the internals of OpenRocket. In particular, | ||
+ | we need to talk about the simulation status, flight data, and simulation listeners. | ||
+ | |||
+ | ===Simulation Status and Flight Data=== | ||
+ | |||
+ | As a simulation proceeds, it maintains its state in a <code>SimulationStatus</code>. The <code>SimulationStatus</code> object contains information about the rocket's current position, orientation, velocity and simulation state. It also contains a reference to a copy of the rocket design and its configuration. Any simulation listener method may modify the state of the rocket by changing the properties of the state object. | ||
+ | |||
+ | Assuming the simulation status is stored in <code>status</code>, we obtain the flight data for the currently executing simulation branch by calling <code>status.getFlightData()</code>. | ||
+ | |||
+ | OpenRocket refers to simulation variables as <code>FlightDataType</code>s, which are <code>List<Double></code>s, with one list for each simulation variable. To obtain a <code>FlightDataType</code>, for example the current motor mass, from <code>flightData</code>, we call <code>flightData.get(FlightDataType.TYPE_MOTOR_MASS))</code>. The standard <code>FlightDataType</code>s are all created in <code>core/src/net/sf/openrocket/simulation/FlightDataType.java</code>; the mechanism for creating a new <code>FlightDataType</code> for your extension will be described later. | ||
+ | |||
+ | Data from the current simulation step can be obtained with e.g. <code>flightData.getLast(FlightDataType.TYPE_MOTOR_MASS</code>. | ||
+ | |||
+ | ===Simulation Listeners=== | ||
+ | |||
+ | Simulation listeners are methods that OpenRocket calls at specified points in the computation to either record information or modify the simulation state. These are divided into three interface classes, named <code>SimulationListener</code>, <code>SimulationComputationListener</code>, and <code>SimulationEventListener</code>. | ||
+ | |||
+ | All of these interfaces are implemented by the abstract class <code>AbstractSimulationListener</code>. This class provides empty methods for all of the methods defined in the three interfaces, which are overridden as needed when writing a listener. A typical listener method (which is actually in the Air-start listener), would be | ||
+ | |||
+ | <pre> | ||
+ | public void startSimulation(SimulationStatus status) throws SimulationException { | ||
+ | status.setRocketPosition(new Coordinate(0, 0, getLaunchAltitude())); | ||
+ | status.setRocketVelocity(status.getRocketOrientationQuaternion().rotate(new Coordinate(0, 0, getLaunchVelocity()))); | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | This method is called when the simulation is first started. It obtains the desired launch altitude and velocity from its configuration, configuration, and inserts them into the simulation status. | ||
+ | |||
+ | The full set of listener methods, with documentation regarding when they are called, can be found in <code>core/src/net/sf/openrocket/AbstractSimulationListener.java</code>. | ||
+ | |||
+ | The listener methods can have three return value types: | ||
+ | |||
+ | * The <code>startSimulation</code>, <code>endSimulation</code>, and <code>postStep</code> are called at a specific point of the simulation. They are void methods and do not return any value. | ||
+ | |||
+ | * The <code>preStep</code> and event-related hooks return a boolean value indicating whether the action should be taken or not. A return value of <code>true</code> indicates that the action should be taken as normally would be (default), <code>false</code> will inhibit the action. | ||
+ | |||
+ | * The pre- and post-computation methods may return the computed value, either as an object or a double value. The pre-computation methods allow pre-empting the entire computation, while the post-computation methods allow augmenting the computed values. These methods may return <code>null</tt> or <code>Double.NaN</code> to use the original values (default), or return an overriding value. | ||
+ | |||
+ | Every method receives a <code>SimulationStatus</code> (see above) object as the first argument, and may have additional arguments. | ||
+ | |||
+ | Each listener method may also throw a <code>SimulationException</code>. This is considered an error during simulation, and an error dialog is displayed to the user with the exception message. The simulation data thus far is not stored in the simulation. Throwing a <code>RuntimeException</code> is considered a bug in the software and will result in a bug report dialog. | ||
+ | |||
+ | If a simulation listener wants to stop a simulation prematurely without an error condition, it needs to add a flight event of type <code>FlightEvent.SIMULATION_END</code> to the simulation event queue: | ||
+ | <pre> | ||
+ | status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime(), null)); | ||
+ | </pre> | ||
+ | This will cause the simulation to be terminated normally. | ||
+ | |||
+ | === Creating a Simulation Extension === | ||
Creating an extension for OpenRocket requires writing three classes: | Creating an extension for OpenRocket requires writing three classes: | ||
Line 28: | Line 78: | ||
* A listener, which extends <code>AbstractSimulationListener</code>. This will be the bulk of your extension, and performs all the real work. | * A listener, which extends <code>AbstractSimulationListener</code>. This will be the bulk of your extension, and performs all the real work. | ||
− | * An extension, which extends <code>AbstractSimulationExtension</code> | + | * An extension, which extends <code>AbstractSimulationExtension</code>. This inserts your listener into the simulation when it is called. Your listener can (and probably will) be private within your extension. |
− | * A provider, which extends <code>AbstractSimulationExtensionProvider</code> | + | * A provider, which extends <code>AbstractSimulationExtensionProvider</code>/ This puts your extension into the menu described above and calls the simulation. |
− | In addition, if your extension will have a configuration GUI, you will need to write | + | In addition, if your extension will have a configuration GUI, you will need to write: |
* A configurator, which extends <code>AbstractSwingSimulationExtensionConfigurator<E></code> | * A configurator, which extends <code>AbstractSwingSimulationExtensionConfigurator<E></code> | ||
Line 44: | Line 94: | ||
<code>swing/src/net/sf/openrocket/simulation/extension/example/</code> | <code>swing/src/net/sf/openrocket/simulation/extension/example/</code> | ||
− | === | + | ===A Simple Example=== |
− | + | To explore the process of writing an extension, we'll begin with a simple example: an extension that writes <code>Hello World</code> to the standard output after every simulation step. The extension is as follows: | |
+ | <pre> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</pre> | </pre> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− |
Revision as of 20:53, 9 December 2022
By using OpenRocket's extension and listener mechanism, it's possible to modify the program itself to add features that are not supported by the program as distributed; some extensions that have been created already provide the ability to air-start a rocket, to add active roll control, and to calculate and save extra flight data.
This page will discuss extensions and simulations. We'll start by showing how a simulation is executed (so you can get a taste of what's possible), and then document the process of creating the extension. WARNING: writing an extension inserts new code into the program. It is entirely possible to disrupt a simulation in a way that invalidates simulation results, or can even crash the program. Be careful!
Adding an Existing Extension to a Simulation
Extensions are added to a simulation through a menu in the "Simulation Options" tab.
- Open a .ork file and go to the Flight Simulations tab
- Click the Edit simulation button to open the Edit simulation dialog.
- Go to the Simulation options tab.
- Click the Add extension button
This will open a menu similar to the one in the following screenshot:
Clicking on the name of an extension will add it to the simulation; if it has a configuration dialog the dialog will be opened:
In the case of the air-start extension, the configuration dialog allows you to set the altitude and velocity at which your simulation will begin. After you close the configuration dialog (if any), a new panel will be added to the Simulation options pane, showing the new extension with buttons to reconfigure it, obtain information about it, or remove it from the simulation:
Some Preliminary Concepts
Before we can discuss writing an extension, we need to briefly discuss some of the internals of OpenRocket. In particular, we need to talk about the simulation status, flight data, and simulation listeners.
Simulation Status and Flight Data
As a simulation proceeds, it maintains its state in a SimulationStatus
. The SimulationStatus
object contains information about the rocket's current position, orientation, velocity and simulation state. It also contains a reference to a copy of the rocket design and its configuration. Any simulation listener method may modify the state of the rocket by changing the properties of the state object.
Assuming the simulation status is stored in status
, we obtain the flight data for the currently executing simulation branch by calling status.getFlightData()
.
OpenRocket refers to simulation variables as FlightDataType
s, which are List<Double>
s, with one list for each simulation variable. To obtain a FlightDataType
, for example the current motor mass, from flightData
, we call flightData.get(FlightDataType.TYPE_MOTOR_MASS))
. The standard FlightDataType
s are all created in core/src/net/sf/openrocket/simulation/FlightDataType.java
; the mechanism for creating a new FlightDataType
for your extension will be described later.
Data from the current simulation step can be obtained with e.g. flightData.getLast(FlightDataType.TYPE_MOTOR_MASS
.
Simulation Listeners
Simulation listeners are methods that OpenRocket calls at specified points in the computation to either record information or modify the simulation state. These are divided into three interface classes, named SimulationListener
, SimulationComputationListener
, and SimulationEventListener
.
All of these interfaces are implemented by the abstract class AbstractSimulationListener
. This class provides empty methods for all of the methods defined in the three interfaces, which are overridden as needed when writing a listener. A typical listener method (which is actually in the Air-start listener), would be
public void startSimulation(SimulationStatus status) throws SimulationException { status.setRocketPosition(new Coordinate(0, 0, getLaunchAltitude())); status.setRocketVelocity(status.getRocketOrientationQuaternion().rotate(new Coordinate(0, 0, getLaunchVelocity()))); }
This method is called when the simulation is first started. It obtains the desired launch altitude and velocity from its configuration, configuration, and inserts them into the simulation status.
The full set of listener methods, with documentation regarding when they are called, can be found in core/src/net/sf/openrocket/AbstractSimulationListener.java
.
The listener methods can have three return value types:
- The
startSimulation
,endSimulation
, andpostStep
are called at a specific point of the simulation. They are void methods and do not return any value.
- The
preStep
and event-related hooks return a boolean value indicating whether the action should be taken or not. A return value oftrue
indicates that the action should be taken as normally would be (default),false
will inhibit the action.
- The pre- and post-computation methods may return the computed value, either as an object or a double value. The pre-computation methods allow pre-empting the entire computation, while the post-computation methods allow augmenting the computed values. These methods may return
null or
Double.NaN
to use the original values (default), or return an overriding value.
Every method receives a SimulationStatus
(see above) object as the first argument, and may have additional arguments.
Each listener method may also throw a SimulationException
. This is considered an error during simulation, and an error dialog is displayed to the user with the exception message. The simulation data thus far is not stored in the simulation. Throwing a RuntimeException
is considered a bug in the software and will result in a bug report dialog.
If a simulation listener wants to stop a simulation prematurely without an error condition, it needs to add a flight event of type FlightEvent.SIMULATION_END
to the simulation event queue:
status.getEventQueue().add(new FlightEvent(FlightEvent.Type.SIMULATION_END, status.getSimulationTime(), null));
This will cause the simulation to be terminated normally.
Creating a Simulation Extension
Creating an extension for OpenRocket requires writing three classes:
- A listener, which extends
AbstractSimulationListener
. This will be the bulk of your extension, and performs all the real work.
- An extension, which extends
AbstractSimulationExtension
. This inserts your listener into the simulation when it is called. Your listener can (and probably will) be private within your extension.
- A provider, which extends
AbstractSimulationExtensionProvider
/ This puts your extension into the menu described above and calls the simulation.
In addition, if your extension will have a configuration GUI, you will need to write:
- A configurator, which extends
AbstractSwingSimulationExtensionConfigurator<E>
You can either create your extension outside the source tree and insert it in OpenRocket's .jar file when compiled, or you can insert it in the source tree and compile it with OpenRocket. Since all of OpenRocket's code is freely available, and reading the code for the existing extensions will be very helpful in writing your's, the easiest approach is to simply insert it in the source tree. If you select this option, a very logical place to put your extension is in
core/src/net/sf/openrocket/simulation/extension/example/
This is where the extensions provided with OpenRocket are defined. Your configurator, if any, will logically go in
swing/src/net/sf/openrocket/simulation/extension/example/
A Simple Example
To explore the process of writing an extension, we'll begin with a simple example: an extension that writes Hello World
to the standard output after every simulation step. The extension is as follows: