diff --git a/core/src/net/sf/openrocket/document/Simulation.java b/core/src/net/sf/openrocket/document/Simulation.java index 3143eb83d..9a75dbbf8 100644 --- a/core/src/net/sf/openrocket/document/Simulation.java +++ b/core/src/net/sf/openrocket/document/Simulation.java @@ -308,7 +308,7 @@ public class Simulation implements ChangeSource, Cloneable { */ public Status getStatus() { mutex.verify(); - if (status == Status.UPTODATE || status == Status.LOADED) { + if (isStatusUpToDate(status)) { if (rocket.getFunctionalModID() != simulatedRocketID || !options.equals(simulatedConditions)) { status = Status.OUTDATED; } @@ -330,6 +330,14 @@ public class Simulation implements ChangeSource, Cloneable { return status; } + + /** + * Returns true is the status indicates that the simulation data is up-to-date. + * @param status status of the simulation to check for if its data is up-to-date + */ + public static boolean isStatusUpToDate(Status status) { + return status == Status.UPTODATE || status == Status.LOADED || status == Status.EXTERNAL; + } diff --git a/core/test/net/sf/openrocket/simulation/DisableStageTest.java b/core/test/net/sf/openrocket/simulation/DisableStageTest.java index 1162a7ef0..bbd62d4cf 100644 --- a/core/test/net/sf/openrocket/simulation/DisableStageTest.java +++ b/core/test/net/sf/openrocket/simulation/DisableStageTest.java @@ -6,8 +6,6 @@ import net.sf.openrocket.rocketcomponent.FlightConfigurationId; import net.sf.openrocket.rocketcomponent.Rocket; import net.sf.openrocket.simulation.exception.MotorIgnitionException; import net.sf.openrocket.simulation.exception.SimulationException; -import net.sf.openrocket.simulation.listeners.AbstractSimulationListener; -import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.util.BaseTestCase.BaseTestCase; import net.sf.openrocket.util.TestRockets; import org.junit.Assert; @@ -35,11 +33,9 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - // Since there are no stages, the simulation should throw an exception. try { - simDisabled.simulate(simulationListener); + simDisabled.simulate(); } catch (SimulationException e) { if (!(e instanceof MotorIgnitionException)) { Assert.fail("Simulation should have thrown a MotorIgnitionException"); @@ -56,7 +52,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); // Re-enable all stages. - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -83,9 +79,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - - compareSims(simRemoved, simDisabled, simulationListener, delta); + compareSims(simRemoved, simDisabled, delta); //// Test re-enableing the stage. Rocket rocketOriginal = TestRockets.makeBeta(); @@ -96,7 +90,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -173,9 +167,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - - compareSims(simRemoved, simDisabled, simulationListener, delta); + compareSims(simRemoved, simDisabled, delta); //// Test re-enableing the stage. Rocket rocketOriginal = TestRockets.makeFalcon9Heavy(); @@ -186,7 +178,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -214,11 +206,9 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getOptions().setISAAtmosphere(true); simDisabled.getOptions().setTimeStep(0.05); - SimulationListener simulationListener = new AbstractSimulationListener(); - // There should be no motors left at this point, so a no motors exception should be thrown try { - simRemoved.simulate(simulationListener); + simRemoved.simulate(); } catch (SimulationException e) { if (!(e instanceof MotorIgnitionException)) { Assert.fail("Simulation failed: " + e); @@ -226,7 +216,7 @@ public class DisableStageTest extends BaseTestCase { } try { - simDisabled.simulate(simulationListener); + simDisabled.simulate(); } catch (SimulationException e) { if (!(e instanceof MotorIgnitionException)) { Assert.fail("Simulation failed: " + e); @@ -242,7 +232,7 @@ public class DisableStageTest extends BaseTestCase { simDisabled.getActiveConfiguration().setAllStages(); - compareSims(simOriginal, simDisabled, simulationListener, delta); + compareSims(simOriginal, simDisabled, delta); } /** @@ -259,13 +249,11 @@ public class DisableStageTest extends BaseTestCase { * - groundHitVelocity * @param simExpected the expected simulation results * @param simActual the actual simulation results - * @param simulationListener the simulation listener to use for the comparison * @param delta the error margin for the comparison (e.g. 0.05 = 5 % error margin) */ - private void compareSims(Simulation simExpected, Simulation simActual, - SimulationListener simulationListener, double delta) { + private void compareSims(Simulation simExpected, Simulation simActual, double delta) { try { - simExpected.simulate(simulationListener); + simExpected.simulate(); double maxAltitudeOriginal = simExpected.getSimulatedData().getMaxAltitude(); double maxVelocityOriginal = simExpected.getSimulatedData().getMaxVelocity(); double maxMachNumberOriginal = simExpected.getSimulatedData().getMaxMachNumber(); @@ -274,7 +262,7 @@ public class DisableStageTest extends BaseTestCase { double launchRodVelocityOriginal = simExpected.getSimulatedData().getLaunchRodVelocity(); double deploymentVelocityOriginal = simExpected.getSimulatedData().getDeploymentVelocity(); - simActual.simulate(simulationListener); + simActual.simulate(); double maxAltitudeDisabled = simActual.getSimulatedData().getMaxAltitude(); double maxVelocityDisabled = simActual.getSimulatedData().getMaxVelocity(); double maxMachNumberDisabled = simActual.getSimulatedData().getMaxMachNumber(); diff --git a/core/test/net/sf/openrocket/simulation/FlightEventsTest.java b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java new file mode 100644 index 000000000..037290746 --- /dev/null +++ b/core/test/net/sf/openrocket/simulation/FlightEventsTest.java @@ -0,0 +1,107 @@ +package net.sf.openrocket.simulation; + +import net.sf.openrocket.document.Simulation; +import net.sf.openrocket.rocketcomponent.FlightConfigurationId; +import net.sf.openrocket.rocketcomponent.Rocket; +import net.sf.openrocket.simulation.exception.SimulationException; +import net.sf.openrocket.util.BaseTestCase.BaseTestCase; +import net.sf.openrocket.util.TestRockets; +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Tests to verify that simulations contain all the expected flight events. + */ +public class FlightEventsTest extends BaseTestCase { + /** + * Tests for a single stage design. + */ + @Test + public void testSingleStage() throws SimulationException { + Rocket rocket = TestRockets.makeEstesAlphaIII(); + Simulation sim = new Simulation(rocket); + sim.getOptions().setISAAtmosphere(true); + sim.getOptions().setTimeStep(0.05); + sim.setFlightConfigurationId(TestRockets.TEST_FCID_0); + + sim.simulate(); + + // Test branch count + int branchCount = sim.getSimulatedData().getBranchCount(); + assertEquals(" Single stage simulation invalid branch count", 1, branchCount); + + FlightEvent.Type[] expectedEventTypes = {FlightEvent.Type.LAUNCH, FlightEvent.Type.IGNITION, FlightEvent.Type.LIFTOFF, + FlightEvent.Type.LAUNCHROD, FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.RECOVERY_DEVICE_DEPLOYMENT, + FlightEvent.Type.APOGEE, FlightEvent.Type.GROUND_HIT, FlightEvent.Type.SIMULATION_END}; + + // Test event count + FlightDataBranch branch = sim.getSimulatedData().getBranch(0); + List eventList = branch.getEvents(); + List eventTypes = eventList.stream().map(FlightEvent::getType).collect(Collectors.toList()); + assertEquals(" Single stage simulation invalid number of events", expectedEventTypes.length, eventTypes.size()); + + // Test that all expected events are present, and in the right order + for (int i = 0; i < expectedEventTypes.length; i++) { + assertSame(" Flight type " + expectedEventTypes[i] + " not found in single stage simulation", + eventTypes.get(i), expectedEventTypes[i]); + } + } + + /** + * Tests for a multi-stage design. + */ + @Test + public void testMultiStage() throws SimulationException { + Rocket rocket = TestRockets.makeFalcon9Heavy(); + Simulation sim = new Simulation(rocket); + sim.getOptions().setISAAtmosphere(true); + sim.getOptions().setTimeStep(0.05); + rocket.getSelectedConfiguration().setAllStages(); + FlightConfigurationId fcid = rocket.getSelectedConfiguration().getFlightConfigurationID(); + sim.setFlightConfigurationId(fcid); + + sim.simulate(); + + // Test branch count + int branchCount = sim.getSimulatedData().getBranchCount(); + assertEquals(" Multi-stage simulation invalid branch count", 3, branchCount); + + for (int b = 0; b < 3; b++) { + FlightEvent.Type[] expectedEventTypes; + switch (b) { + case 0: + expectedEventTypes = new FlightEvent.Type[]{FlightEvent.Type.LAUNCH, FlightEvent.Type.IGNITION, FlightEvent.Type.IGNITION, + FlightEvent.Type.LIFTOFF, FlightEvent.Type.LAUNCHROD, FlightEvent.Type.APOGEE, FlightEvent.Type.BURNOUT, + FlightEvent.Type.BURNOUT, FlightEvent.Type.EJECTION_CHARGE, FlightEvent.Type.EJECTION_CHARGE, + FlightEvent.Type.STAGE_SEPARATION, FlightEvent.Type.STAGE_SEPARATION, FlightEvent.Type.TUMBLE, FlightEvent.Type.GROUND_HIT, + FlightEvent.Type.SIMULATION_END}; + break; + case 1: + case 2: + expectedEventTypes = new FlightEvent.Type[]{FlightEvent.Type.TUMBLE, FlightEvent.Type.GROUND_HIT, + FlightEvent.Type.SIMULATION_END}; + break; + default: + throw new IllegalStateException("Invalid branch number " + b); + } + + // Test event count + FlightDataBranch branch = sim.getSimulatedData().getBranch(b); + List eventList = branch.getEvents(); + List eventTypes = eventList.stream().map(FlightEvent::getType).collect(Collectors.toList()); + assertEquals(" Multi-stage simulation, branch " + b + " invalid number of events", expectedEventTypes.length, eventTypes.size()); + + // Test that all expected events are present, and in the right order + for (int i = 0; i < expectedEventTypes.length; i++) { + assertSame(" Flight type " + expectedEventTypes[i] + ", branch " + b + " not found in multi-stage simulation", + eventTypes.get(i), expectedEventTypes[i]); + } + } + } + +} diff --git a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java index 9f702dd22..04c657986 100644 --- a/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java +++ b/swing/src/net/sf/openrocket/gui/main/SimulationPanel.java @@ -504,8 +504,7 @@ public class SimulationPanel extends JPanel { private void openDialog(final Simulation sim) { boolean plotMode = false; - if (sim.hasSimulationData() && (sim.getStatus() == Status.UPTODATE || sim.getStatus() == Status.LOADED - || sim.getStatus() == Status.EXTERNAL)) { + if (sim.hasSimulationData() && Simulation.isStatusUpToDate(sim.getStatus())) { plotMode = true; } openDialog(plotMode, sim); diff --git a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java index 9900305b4..da0a95dd1 100644 --- a/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java +++ b/swing/src/net/sf/openrocket/gui/plot/SimulationPlot.java @@ -60,7 +60,7 @@ import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; /* - * It should be possible to simplify this code quite a bit by using a single Renderer instance for + * TODO: It should be possible to simplify this code quite a bit by using a single Renderer instance for * both datasets and the legend. But for now, the renderers are queried for the line color information * and this is held in the Legend. */ diff --git a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java index bda631bc3..567495c0c 100644 --- a/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java +++ b/swing/src/net/sf/openrocket/gui/scalefigure/RocketPanel.java @@ -857,8 +857,8 @@ public class RocketPanel extends JPanel implements TreeSelectionListener, Change // Re-run the present simulation(s) List sims = new LinkedList<>(); for (Simulation sim : document.getSimulations()) { - if (sim.getStatus() == Simulation.Status.UPTODATE || sim.getStatus() == Simulation.Status.LOADED - || !document.getRocket().getFlightConfiguration(sim.getFlightConfigurationId()).hasMotors()) + if (Simulation.isStatusUpToDate(sim.getStatus()) || + !document.getRocket().getFlightConfiguration(sim.getFlightConfigurationId()).hasMotors()) continue; // Find a Simulation based on the current flight configuration diff --git a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java index 03b9ab944..7955bdae3 100644 --- a/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java +++ b/swing/src/net/sf/openrocket/gui/simulation/SimulationEditDialog.java @@ -292,8 +292,7 @@ public class SimulationEditDialog extends JDialog { @Override public void actionPerformed(ActionEvent e) { // If the simulation is out of date, run the simulation. - if (simulationList[0].getStatus() != Simulation.Status.UPTODATE && - simulationList[0].getStatus() != Simulation.Status.LOADED) { + if (!Simulation.isStatusUpToDate(simulationList[0].getStatus())) { new SimulationRunDialog(SimulationEditDialog.this.parentWindow, document, simulationList[0]).setVisible(true); }