From 4a3b9efe763e779bd11eef97f485b06b92e4dbf7 Mon Sep 17 00:00:00 2001
From: SiboVG <sibo.vangool@hotmail.com>
Date: Sat, 21 Sep 2024 06:33:06 +0100
Subject: [PATCH] Properly fire changes after wind model change

---
 .../wind/MultiLevelPinkNoiseWindModel.java    | 61 ++++++++++++++++++-
 .../core/models/wind/PinkNoiseWindModel.java  | 27 ++++++++
 .../core/models/wind/WindModel.java           | 29 ---------
 .../core/simulation/SimulationOptions.java    |  2 +
 4 files changed, 89 insertions(+), 30 deletions(-)

diff --git a/core/src/main/java/info/openrocket/core/models/wind/MultiLevelPinkNoiseWindModel.java b/core/src/main/java/info/openrocket/core/models/wind/MultiLevelPinkNoiseWindModel.java
index 58954ad36..62365ef64 100644
--- a/core/src/main/java/info/openrocket/core/models/wind/MultiLevelPinkNoiseWindModel.java
+++ b/core/src/main/java/info/openrocket/core/models/wind/MultiLevelPinkNoiseWindModel.java
@@ -1,15 +1,22 @@
 package info.openrocket.core.models.wind;
 
 import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.EventObject;
 import java.util.List;
 import java.util.Collections;
 import java.util.Comparator;
+
+import info.openrocket.core.util.ChangeSource;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.ModID;
+import info.openrocket.core.util.StateChangeListener;
 
 public class MultiLevelPinkNoiseWindModel implements WindModel {
 	private List<LevelWindModel> levels;
 
+	private final List<StateChangeListener> listeners = new ArrayList<>();
+
 	public MultiLevelPinkNoiseWindModel() {
 		this.levels = new ArrayList<>();
 	}
@@ -21,23 +28,28 @@ public class MultiLevelPinkNoiseWindModel implements WindModel {
 		pinkNoiseModel.setDirection(direction);
 
 		LevelWindModel newLevel = new LevelWindModel(altitude, pinkNoiseModel);
+		newLevel.addChangeListener(e -> fireChangeEvent());
 		int index = Collections.binarySearch(levels, newLevel, Comparator.comparingDouble(l -> l.altitude));
 		if (index >= 0) {
 			throw new IllegalArgumentException("Wind level already exists for altitude: " + altitude);
 		}
 		levels.add(-index - 1, newLevel);
+		fireChangeEvent();
 	}
 
 	public void removeWindLevel(double altitude) {
 		levels.removeIf(level -> level.altitude == altitude);
+		fireChangeEvent();
 	}
 
 	public void removeWindLevelIdx(int index) {
 		levels.remove(index);
+		fireChangeEvent();
 	}
 
 	public void clearLevels() {
 		levels.clear();
+		fireChangeEvent();
 	}
 
 	public List<LevelWindModel> getLevels() {
@@ -127,10 +139,12 @@ public class MultiLevelPinkNoiseWindModel implements WindModel {
 		return levels.hashCode();
 	}
 
-	public static class LevelWindModel implements Cloneable {
+	public static class LevelWindModel implements Cloneable, ChangeSource {
 		protected double altitude;
 		protected PinkNoiseWindModel model;
 
+		private final List<StateChangeListener> listeners = new ArrayList<>();
+
 		LevelWindModel(double altitude, PinkNoiseWindModel model) {
 			this.altitude = altitude;
 			this.model = model;
@@ -142,6 +156,7 @@ public class MultiLevelPinkNoiseWindModel implements WindModel {
 
 		public void setAltitude(double altitude) {
 			this.altitude = altitude;
+			fireChangeEvent();
 		}
 
 		public double getSpeed() {
@@ -194,5 +209,49 @@ public class MultiLevelPinkNoiseWindModel implements WindModel {
 			LevelWindModel that = (LevelWindModel) obj;
 			return Double.compare(that.altitude, altitude) == 0 && model.equals(that.model);
 		}
+
+		@Override
+		public void addChangeListener(StateChangeListener listener) {
+			listeners.add(listener);
+			model.addChangeListener(listener);
+		}
+
+		@Override
+		public void removeChangeListener(StateChangeListener listener) {
+			listeners.remove(listener);
+			model.removeChangeListener(listener);
+		}
+
+		public void fireChangeEvent() {
+			EventObject event = new EventObject(this);
+			// Copy the list before iterating to prevent concurrent modification exceptions.
+			EventListener[] list = listeners.toArray(new EventListener[0]);
+			for (EventListener l : list) {
+				if (l instanceof StateChangeListener) {
+					((StateChangeListener) l).stateChanged(event);
+				}
+			}
+		}
+	}
+
+	@Override
+	public void addChangeListener(StateChangeListener listener) {
+		listeners.add(listener);
+	}
+
+	@Override
+	public void removeChangeListener(StateChangeListener listener) {
+		listeners.remove(listener);
+	}
+
+	public void fireChangeEvent() {
+		EventObject event = new EventObject(this);
+		// Copy the list before iterating to prevent concurrent modification exceptions.
+		EventListener[] list = listeners.toArray(new EventListener[0]);
+		for (EventListener l : list) {
+			if (l instanceof StateChangeListener) {
+				((StateChangeListener) l).stateChanged(event);
+			}
+		}
 	}
 }
\ No newline at end of file
diff --git a/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java b/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
index a2415cb07..4ebfcd721 100644
--- a/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
+++ b/core/src/main/java/info/openrocket/core/models/wind/PinkNoiseWindModel.java
@@ -1,11 +1,16 @@
 package info.openrocket.core.models.wind;
 
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.List;
 import java.util.Random;
 
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.MathUtil;
 import info.openrocket.core.util.ModID;
 import info.openrocket.core.util.PinkNoise;
+import info.openrocket.core.util.StateChangeListener;
 
 /**
  * A wind simulator that generates wind speed as pink noise from a specified
@@ -46,6 +51,8 @@ public class PinkNoiseWindModel implements WindModel {
 	private double time1;
 	private double value1, value2;
 
+	private final List<StateChangeListener> listeners = new ArrayList<>();
+
 	/**
 	 * Construct a new wind simulation with a specific seed value.
 	 * 
@@ -222,4 +229,24 @@ public class PinkNoiseWindModel implements WindModel {
 		return result;
 	}
 
+	@Override
+	public void addChangeListener(StateChangeListener listener) {
+		listeners.add(listener);
+	}
+
+	@Override
+	public void removeChangeListener(StateChangeListener listener) {
+		listeners.remove(listener);
+	}
+
+	public void fireChangeEvent() {
+		EventObject event = new EventObject(this);
+		// Copy the list before iterating to prevent concurrent modification exceptions.
+		EventListener[] list = listeners.toArray(new EventListener[0]);
+		for (EventListener l : list) {
+			if (l instanceof StateChangeListener) {
+				((StateChangeListener) l).stateChanged(event);
+			}
+		}
+	}
 }
diff --git a/core/src/main/java/info/openrocket/core/models/wind/WindModel.java b/core/src/main/java/info/openrocket/core/models/wind/WindModel.java
index 2e4150ef8..379219d19 100644
--- a/core/src/main/java/info/openrocket/core/models/wind/WindModel.java
+++ b/core/src/main/java/info/openrocket/core/models/wind/WindModel.java
@@ -3,38 +3,9 @@ package info.openrocket.core.models.wind;
 import info.openrocket.core.util.ChangeSource;
 import info.openrocket.core.util.Coordinate;
 import info.openrocket.core.util.Monitorable;
-import info.openrocket.core.util.StateChangeListener;
-
-import java.util.ArrayList;
-import java.util.EventListener;
-import java.util.EventObject;
-import java.util.List;
 
 public interface WindModel extends Monitorable, Cloneable, ChangeSource {
-	List<StateChangeListener> listeners = new ArrayList<>();
-
 	Coordinate getWindVelocity(double time, double altitude);
 
 	WindModel clone();
-
-	@Override
-	default void addChangeListener(StateChangeListener listener) {
-		listeners.add(listener);
-	}
-
-	@Override
-	default void removeChangeListener(StateChangeListener listener) {
-		listeners.remove(listener);
-	}
-
-	default void fireChangeEvent() {
-		EventObject event = new EventObject(this);
-		// Copy the list before iterating to prevent concurrent modification exceptions.
-		EventListener[] list = listeners.toArray(new EventListener[0]);
-		for (EventListener l : list) {
-			if (l instanceof StateChangeListener) {
-				((StateChangeListener) l).stateChanged(event);
-			}
-		}
-	}
 }
diff --git a/core/src/main/java/info/openrocket/core/simulation/SimulationOptions.java b/core/src/main/java/info/openrocket/core/simulation/SimulationOptions.java
index 997a5adf9..ad828f8ee 100644
--- a/core/src/main/java/info/openrocket/core/simulation/SimulationOptions.java
+++ b/core/src/main/java/info/openrocket/core/simulation/SimulationOptions.java
@@ -86,7 +86,9 @@ public class SimulationOptions implements ChangeSource, Cloneable, SimulationOpt
 
 	public SimulationOptions() {
 		averageWindModel = new PinkNoiseWindModel(randomSeed);
+		averageWindModel.addChangeListener(e -> fireChangeEvent());
 		multiLevelPinkNoiseWindModel = new MultiLevelPinkNoiseWindModel();
+		multiLevelPinkNoiseWindModel.addChangeListener(e -> fireChangeEvent());
 	}
 
 	public double getLaunchRodLength() {