Extract common functionality to DataBranch

This commit is contained in:
SiboVG 2024-08-17 16:10:03 +02:00
parent 9554bf3fe2
commit 621ee57cc2
2 changed files with 194 additions and 198 deletions

View File

@ -1,14 +1,133 @@
package info.openrocket.core.simulation; package info.openrocket.core.simulation;
import info.openrocket.core.util.ArrayList;
import info.openrocket.core.util.ModID;
import info.openrocket.core.util.Monitorable; import info.openrocket.core.util.Monitorable;
import info.openrocket.core.util.Mutable;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
/** /**
* A branch of data / collection of data points for a specific type of data. * A branch of data / collection of data points for a specific type of data.
* @param <T> the type of data in this branch * @param <T> the type of data in this branch
*/ */
public interface DataBranch<T extends DataType> extends Monitorable { public abstract class DataBranch<T extends DataType> implements Monitorable {
protected final String name;
protected final Map<T, ArrayList<Double>> values = new LinkedHashMap<>();
protected final Map<T, Double> maxValues = new HashMap<>();
protected final Map<T, Double> minValues = new HashMap<>();
protected final Mutable mutable = new Mutable();
protected ModID modID = ModID.INVALID;
/**
* Sole constructor. Defines the name of the DataBranch and at least one variable type.
*
* @param name the name of this DataBranch.
* @param types data types to include (must include at least one type).
*/
@SafeVarargs
public DataBranch(String name, T... types) {
if (types.length == 0) {
throw new IllegalArgumentException("Must specify at least one data type.");
}
this.name = name;
for (T t : types) {
addType(t);
}
}
public DataBranch(String name) {
this.name = name;
}
protected void addType(T type) {
if (values.containsKey(type)) {
throw new IllegalArgumentException("Value type " + type + " already exists.");
}
values.put(type, new ArrayList<>());
minValues.put(type, Double.NaN);
maxValues.put(type, Double.NaN);
}
/**
* Adds a new point into the data branch. The value for all types is set to NaN by default.
*
* @throws IllegalStateException if this object has been made immutable.
*/
public void addPoint() {
mutable.check();
for (Map.Entry<T, ArrayList<Double>> entry : values.entrySet()) {
sanityCheckValues(entry.getKey(), Double.NaN);
entry.getValue().add(Double.NaN);
}
modID = new ModID();
}
private void sanityCheckValues(T type, Double value) {
ArrayList<Double> list = values.get(type);
if (list == null) {
list = new info.openrocket.core.util.ArrayList<>();
int n = getLength();
for (int i = 0; i < n; i++) {
list.add(Double.NaN);
}
values.put(type, list);
minValues.put(type, value);
maxValues.put(type, value);
}
}
/**
* Set the value for a specific data type at the latest point. New variable types can be
* added to the FlightDataBranch transparently.
*
* @param type the variable to set.
* @param value the value to set.
* @throws IllegalStateException if this object has been made immutable.
*/
public void setValue(T type, double value) {
mutable.check();
ArrayList<Double> list = values.computeIfAbsent(type, k -> {
ArrayList<Double> newList = new ArrayList<>();
int n = getLength();
for (int i = 0; i < n; i++) {
newList.add(Double.NaN);
}
minValues.put(k, Double.NaN);
maxValues.put(k, Double.NaN);
return newList;
});
if (list.size() > 0) {
list.set(list.size() - 1, value);
}
double min = minValues.get(type);
double max = maxValues.get(type);
if (Double.isNaN(min) || (value < min)) {
minValues.put(type, value);
}
if (Double.isNaN(max) || (value > max)) {
maxValues.put(type, value);
}
modID = new ModID();
}
/** /**
* Return an array of values for the specified variable type. * Return an array of values for the specified variable type.
* *
@ -16,7 +135,12 @@ public interface DataBranch<T extends DataType> extends Monitorable {
* @return a list of the variable values, or <code>null</code> if * @return a list of the variable values, or <code>null</code> if
* the variable type hasn't been added to this branch. * the variable type hasn't been added to this branch.
*/ */
List<Double> get(T type); public List<Double> get(T type) {
ArrayList<Double> list = values.get(type);
if (list == null)
return null;
return list.clone();
}
/** /**
* Return the value of the specified type at the specified index. * Return the value of the specified type at the specified index.
@ -24,7 +148,16 @@ public interface DataBranch<T extends DataType> extends Monitorable {
* @param index the data index of the value * @param index the data index of the value
* @return the value at the specified index * @return the value at the specified index
*/ */
Double getByIndex(T type, int index); public Double getByIndex(T type, int index) {
if (index < 0 || index >= getLength()) {
throw new IllegalArgumentException("Index out of bounds");
}
ArrayList<Double> list = values.get(type);
if (list == null) {
return null;
}
return list.get(index);
}
/** /**
* Return the last value of the specified type in the branch, or NaN if the type is * Return the last value of the specified type in the branch, or NaN if the type is
@ -33,7 +166,12 @@ public interface DataBranch<T extends DataType> extends Monitorable {
* @param type the parameter type. * @param type the parameter type.
* @return the last value in this branch, or NaN. * @return the last value in this branch, or NaN.
*/ */
double getLast(T type); public double getLast(T type) {
ArrayList<Double> list = values.get(type);
if (list == null || list.isEmpty())
return Double.NaN;
return list.get(list.size() - 1);
}
/** /**
* Return the minimum value of the specified type in the branch, or NaN if the type * Return the minimum value of the specified type in the branch, or NaN if the type
@ -42,7 +180,12 @@ public interface DataBranch<T extends DataType> extends Monitorable {
* @param type the parameter type. * @param type the parameter type.
* @return the minimum value in this branch, or NaN. * @return the minimum value in this branch, or NaN.
*/ */
double getMinimum(T type); public double getMinimum(T type) {
Double v = minValues.get(type);
if (v == null)
return Double.NaN;
return v;
}
/** /**
* Return the maximum value of the specified type in the branch, or NaN if the type * Return the maximum value of the specified type in the branch, or NaN if the type
@ -51,21 +194,59 @@ public interface DataBranch<T extends DataType> extends Monitorable {
* @param type the parameter type. * @param type the parameter type.
* @return the maximum value in this branch, or NaN. * @return the maximum value in this branch, or NaN.
*/ */
double getMaximum(T type); public double getMaximum(T type) {
Double v = maxValues.get(type);
if (v == null)
return Double.NaN;
return v;
}
/** /**
* Return the number of data points in this branch. * Return the number of data points in this branch.
*/ */
int getLength(); public int getLength() {
for (ArrayList<Double> doubles : values.values()) {
return doubles.size();
}
return 0;
}
/** /**
* Return the variable types included in this branch. The types are sorted in their * Return the variable types included in this branch. The types are sorted in their
* natural order. * natural order.
*/ */
T[] getTypes(); @SuppressWarnings("unchecked")
public T[] getTypes() {
Set<T> keySet = values.keySet();
T[] array = (T[]) Array.newInstance(keySet.iterator().next().getClass(), keySet.size());
keySet.toArray(array);
Arrays.sort(array);
return array;
}
/** /**
* Return the branch name. * Return the branch name.
*/ */
String getName(); public String getName() {
return name;
}
/**
* Make this FlightDataBranch immutable. Any calls to the set methods that would
* modify this object will after this call throw an <code>IllegalStateException</code>.
*/
public void immute() {
mutable.immute();
}
/**
* Return whether this branch is still mutable.
*/
public boolean isMutable() {
return mutable.isMutable();
}
public ModID getModID() {
return modID;
}
} }

View File

@ -1,8 +1,5 @@
package info.openrocket.core.simulation; package info.openrocket.core.simulation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -11,8 +8,6 @@ import info.openrocket.core.rocketcomponent.Rocket;
import info.openrocket.core.rocketcomponent.RocketComponent; import info.openrocket.core.rocketcomponent.RocketComponent;
import info.openrocket.core.util.ArrayList; import info.openrocket.core.util.ArrayList;
import info.openrocket.core.util.ModID; import info.openrocket.core.util.ModID;
import info.openrocket.core.util.Monitorable;
import info.openrocket.core.util.Mutable;
/** /**
* A single branch of flight data. The data is ordered based on some variable, typically time. * A single branch of flight data. The data is ordered based on some variable, typically time.
@ -29,31 +24,11 @@ import info.openrocket.core.util.Mutable;
* *
* @author Sampo Niskanen <sampo.niskanen@iki.fi> * @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/ */
public class FlightDataBranch implements DataBranch<FlightDataType> { public class FlightDataBranch extends DataBranch<FlightDataType> {
/** The name of this flight data branch. */
private final String name;
private final Map<FlightDataType, ArrayList<Double>> values = new LinkedHashMap<>();
private final Map<FlightDataType, Double> maxValues = new HashMap<>();
private final Map<FlightDataType, Double> minValues = new HashMap<>();
/**
* time for the rocket to reach apogee if the flight had been no recovery deployment
*/
private double timeToOptimumAltitude = Double.NaN; private double timeToOptimumAltitude = Double.NaN;
/**
* Altitude the rocket would reach if there had been no recovery deployment.
*/
private double optimumAltitude = Double.NaN; private double optimumAltitude = Double.NaN;
private final ArrayList<FlightEvent> events = new ArrayList<>(); private final ArrayList<FlightEvent> events = new ArrayList<>();
private final Mutable mutable = new Mutable();
private ModID modID = ModID.INVALID;
/** /**
* Sole constructor. Defines the name of the FlightDataBranch and at least one variable type. * Sole constructor. Defines the name of the FlightDataBranch and at least one variable type.
* *
@ -61,22 +36,7 @@ public class FlightDataBranch implements DataBranch<FlightDataType> {
* @param types data types to include (must include at least one type). * @param types data types to include (must include at least one type).
*/ */
public FlightDataBranch(String name, FlightDataType... types) { public FlightDataBranch(String name, FlightDataType... types) {
if (types.length == 0) { super(name, types);
throw new IllegalArgumentException("Must specify at least one data type.");
}
this.name = name;
for (FlightDataType t : types) {
if (values.containsKey(t)) {
throw new IllegalArgumentException("Value type " + t + " specified multiple " +
"times in constructor.");
}
values.put(t, new ArrayList<>());
minValues.put(t, Double.NaN);
maxValues.put(t, Double.NaN);
}
} }
/** /**
@ -89,7 +49,7 @@ public class FlightDataBranch implements DataBranch<FlightDataType> {
* @param parent the parent branch to copy data from. * @param parent the parent branch to copy data from.
*/ */
public FlightDataBranch(String name, RocketComponent srcComponent, FlightDataBranch parent) { public FlightDataBranch(String name, RocketComponent srcComponent, FlightDataBranch parent) {
this.name = name; super(name);
// Copy all the values from the parent // Copy all the values from the parent
copyValuesFromBranch(parent, srcComponent); copyValuesFromBranch(parent, srcComponent);
@ -99,72 +59,13 @@ public class FlightDataBranch implements DataBranch<FlightDataType> {
* Makes an 'empty' flight data branch which has no data but all built in data types are defined. * Makes an 'empty' flight data branch which has no data but all built in data types are defined.
*/ */
public FlightDataBranch() { public FlightDataBranch() {
name = "Empty branch"; super("Empty branch");
for (FlightDataType type : FlightDataType.ALL_TYPES) { for (FlightDataType type : FlightDataType.ALL_TYPES) {
this.setValue(type, Double.NaN); this.setValue(type, Double.NaN);
} }
this.immute(); this.immute();
} }
/**
* Adds a new point into the data branch. The value for all types is set to NaN by default.
*
* @throws IllegalStateException if this object has been made immutable.
*/
public void addPoint() {
mutable.check();
for (Map.Entry<FlightDataType, ArrayList<Double>> entry : values.entrySet()) {
sanityCheckValues(entry.getKey(), Double.NaN);
entry.getValue().add(Double.NaN);
}
modID = new ModID();
}
private void sanityCheckValues(FlightDataType type, Double value) {
ArrayList<Double> list = values.get(type);
if (list == null) {
list = new ArrayList<>();
int n = getLength();
for (int i = 0; i < n; i++) {
list.add(Double.NaN);
}
values.put(type, list);
minValues.put(type, value);
maxValues.put(type, value);
}
}
/**
* Set the value for a specific data type at the latest point. New variable types can be
* added to the FlightDataBranch transparently.
*
* @param type the variable to set.
* @param value the value to set.
* @throws IllegalStateException if this object has been made immutable.
*/
public void setValue(FlightDataType type, double value) {
mutable.check();
sanityCheckValues(type, value);
ArrayList<Double> list = values.get(type);
if (list.size() > 0) {
list.set(list.size() - 1, value);
}
double min = minValues.get(type);
double max = maxValues.get(type);
if (Double.isNaN(min) || (value < min)) {
minValues.put(type, value);
}
if (Double.isNaN(max) || (value > max)) {
maxValues.put(type, value);
}
modID = new ModID();
}
/** /**
* Clears all the current values in the branch and copies the values from the given branch. * Clears all the current values in the branch and copies the values from the given branch.
@ -228,70 +129,6 @@ public class FlightDataBranch implements DataBranch<FlightDataType> {
} }
} }
@Override
public String getName() {
return name;
}
@Override
public FlightDataType[] getTypes() {
FlightDataType[] array = values.keySet().toArray(new FlightDataType[0]);
Arrays.sort(array);
return array;
}
@Override
public int getLength() {
for (ArrayList<Double> doubles : values.values()) {
return doubles.size();
}
return 0;
}
@Override
public List<Double> get(FlightDataType type) {
ArrayList<Double> list = values.get(type);
if (list == null)
return null;
return list.clone();
}
@Override
public Double getByIndex(FlightDataType type, int index) {
if (index < 0 || index >= getLength()) {
throw new IllegalArgumentException("Index out of bounds");
}
ArrayList<Double> list = values.get(type);
if (list == null) {
return null;
}
return list.get(index);
}
@Override
public double getLast(FlightDataType type) {
ArrayList<Double> list = values.get(type);
if (list == null || list.isEmpty())
return Double.NaN;
return list.get(list.size() - 1);
}
@Override
public double getMinimum(FlightDataType type) {
Double v = minValues.get(type);
if (v == null)
return Double.NaN;
return v;
}
@Override
public double getMaximum(FlightDataType type) {
Double v = maxValues.get(type);
if (v == null)
return Double.NaN;
return v;
}
/** /**
* @return the timeToOptimumAltitude * @return the timeToOptimumAltitude
@ -388,28 +225,6 @@ public class FlightDataBranch implements DataBranch<FlightDataType> {
return retval; return retval;
} }
/**
* Make this FlightDataBranch immutable. Any calls to the set methods that would
* modify this object will after this call throw an <code>IllegalStateException</code>.
*/
public void immute() {
mutable.immute();
}
/**
* Return whether this branch is still mutable.
*/
public boolean isMutable() {
return mutable.isMutable();
}
@Override
public ModID getModID() {
return modID;
}
public FlightDataBranch clone() { public FlightDataBranch clone() {
FlightDataType[] types = getTypes(); FlightDataType[] types = getTypes();
FlightDataBranch clone = new FlightDataBranch(name, types); FlightDataBranch clone = new FlightDataBranch(name, types);